package com.google.code.jouka.utils;


import java.text.NumberFormat;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * A useful String utilities.
 *
 * @author Str.Isym
 *
 */
public class StrUtil {

    public final static char[] IGNORE_QUOTES = {};
    public final static char[] IGNORE_BRACKETS = IGNORE_QUOTES;
    public final static char[] IGNORE_EXCEPTED_CHARS = IGNORE_QUOTES;

    private final static String[] WILDCARDS_LONG   = {"$+", "~+", "&+", "#+", "@+", "?+","`+", "{{", "}}"};
    private final static int SPACEs_OR_TABs   = 0;
    private final static int CRs_OR_LFs       = 1;
    private final static int ANY_BLANKs       = 2;
    private final static int ANY_NUMBERs      = 3;
    private final static int ANY_ALPHABETs    = 4;
    private final static int ALPHAs_NUMs_BARs = 5;
    private final static int URL_ELEMENTs     = 6;
    private final static int COMMAND_OPEN     = 7;
    private final static int COMMAND_CLOSE    = 8;

    private final static char[]  WILDCARDS_SHORT  = {'%', '_', '$', '~', '&', '#', '@', '?', '`'};
//    private final static int ANY_WORD      = 0;
    private final static int CHAR          = 1;
    private final static int SPACE_OR_TAB  = 2;
    private final static int CR_OR_LF      = 3;
    private final static int ANY_BLANK     = 4;
    private final static int ANY_NUMBER    = 5;
    private final static int ANY_ALPHABET  = 6;
    private final static int ALPHA_NUM_BAR = 7;
    private final static int URL_ELEMENT   = 8;

    private final static char[]  COMMANDS  = {'*', '!'};
    private final static int CMD_OR  = 0;
//    private final static int CMD_NOT = 1;

    private final static int CLASS_NOT_WILDCARDS   = 0;
    private final static int CLASS_WILDCARDS_LONG  = 1;
    private final static int CLASS_WILDCARDS_SHORT = 2;

    private static final char CR = '\r';
    private static final char LF = '\n';
    private static final char TAB = '\t';
    private static final char SPC = ' ';
    private static final char DQ = '"';
    private static final char SQ = '\'';

    public static char[]   DEFAULT_QUOTES          = {SQ, DQ};
    public static char[]   DEFAULT_BRACKETS        = {'<', '>', '(', ')', '[', ']', '{', '}'};
    public static char[]   DEFAULT_EXCEPTED_CHARS  = {'=', '|'};

    public static final char[] URIC = {
            ';', '/', '?', ':', '@', '&', '=', '+', '$', ',',
            '-', '_', '.', '!', '~', '*', '\'', '(', ')',
            '%'};


    /**
     * <pre>
     * 対象文字列に、任意の文字列が何個含まれているかを返す。
     * 大文字小文字は区別される。
     *
     * Example:
     *   StrUtil.count("abc123ABC", "xyz")  = 0
     *   StrUtil.count("abcdabc1ab", "abc") = 2
     *   StrUtil.count("01001010", "010")   = 2
     *   StrUtil.count("0100101010", "010") = 3
     *
     * </pre>
     * @param value
     * @param keyword
     * @return 文字の個数
     */
    public static int count(String value, String keyword) {
        if (isEmpty(value) || isEmpty(keyword)) {
            return 0;
        }
        int count = 0;
        int index = 0;
        while ((index = value.indexOf(keyword, index)) != -1) {
            count++;
            index += keyword.length();
        }
        return count;
    }


    /**
     * <pre>
     * 対象文字列に、任意の文字が何個含まれているかを返す。
     * 大文字小文字は区別される。
     *
     * Example:
     *   StrUtil.count("abc123ABC", 'x')  = 0
     *   StrUtil.count("abc123ABC", 'a')  = 1
     *   StrUtil.count("abc123abc", 'a')  = 2
     *   StrUtil.count("0100101010", '0') = 6
     *
     * </pre>
     * @param value
     * @param keyword
     * @return 文字の個数
     */
    public static int count(String value, char keyword){
        if (isEmpty(value)) {
            return 0;
        }
        int count = 0;
        int index = 0;
        while ((index = value.indexOf(keyword, index)) != -1) {
            count++;
            index ++;
        }
        return count;

    }


    /**
     * <pre>
     * 対象文字列の先頭から指定のインデックス以降、任意の検索文字が連続して
     * 何個含まれているかを返す。
     * 連続した文字ブロックは最初のブロックのみ認識される。
     * 大文字小文字は区別される。
     *
     * Example:
     *   StrUtil.countContinued("AAA---BBBB", 'A', 1) = 2
     *   StrUtil.countContinued("AAA---BBBB", 'A', 2) = 1
     *   StrUtil.countContinued("AAA---BBBB", 'A', 3) = 0
     *   StrUtil.countContinued("AAA---BBBB", 'B', 6) = 4
     *   StrUtil.countContinued("AAA---BBBB", 'B', 7) = 3
     *   StrUtil.countContinued("AAA---BBBB", 'B', 8) = 2
     *   StrUtil.countContinued("AAA---BBBB", 'B', 9) = 1
     *   StrUtil.countContinued("AAA---BBBB", 'B',10) = 0
     *
     * </pre>
     * @param value 対象文字列
     * @param keyword 検索文字
     * @param offset 検索開始オフセットインデックス
     * @return 連続した文字の個数
     */
    public static int countContinued(String value, char keyword, int offset){
        if (isEmpty(value)) {
            return 0;
        }
        char prev;
        char current;
        int count = 0;
        int index = value.indexOf(keyword, offset);
        if(index > -1){
            prev = value.charAt(index);
            index++;
            if(index >= value.length()){
                return 1;
            }
            current = value.charAt(index);
            if(prev != current){
                return 1;
            }
            count++;
            while(true){
                prev    = current;
                current = value.charAt(index);
                if(prev != current){
                    break;
                }else{
                    count++;
                    index++;
                    if(index >= value.length()){
                        break;
                    }
                }
            }
        }
        return count;
    }


    /**
     * <pre>
     * 対象文字列の先頭(index=0)から任意の検索文字が連続して
     * 何個含まれているかを返す。
     * 連続した文字ブロックは最初のブロックのみ認識される。
     * 大文字小文字は区別される。
     *
     * Example:
     *   StrUtil.countContinued(""          , 'A') = 0
     *   StrUtil.countContinued("-"         , 'A') = 0
     *   StrUtil.countContinued("---A"      , 'A') = 1
     *   StrUtil.countContinued("A---"      , 'A') = 1
     *   StrUtil.countContinued("AA---"     , 'A') = 2
     *   StrUtil.countContinued("AAA---AA"  , 'A') = 3
     *   StrUtil.countContinued("---AA"     , 'A') = 2
     *   StrUtil.countContinued("--AAAA-AA" , 'A') = 4
     *   StrUtil.countContinued("AAA---BBBB", 'A') = 3
     *   StrUtil.countContinued("AAA---BBBB", 'B') = 4
     *   StrUtil.countContinued(null        , 'A') = 0
     *
     *
     * </pre>
     * @param value
     * @param keyword
     * @return countContinuedChars(target, search, 0)
     */
    public static int countContinued(String value, char keyword){
        return countContinued(value, keyword, 0);

    }


    /**
     * <pre>
     * 対象文字列の末尾から指定のインデックス以前に、任意の検索文字が連続して
     * 前方に向かって何個含まれているかを返す。
     * 連続した文字ブロックは初めに発見したブロックのみ認識される。
     * 大文字小文字は区別される。
     *
     * Example:
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'B', 99) = 3
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'B',  9) = 3
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'B',  8) = 2
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'B',  7) = 1
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'B',  6) = 0
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'A',  9) = 4
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'A',  3) = 4
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'A',  2) = 3
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'A',  1) = 2
     *   StrUtil.countContinuedFromEnd("AAAA---BBB", 'A',  0) = 1
     *
     * </pre>
     * @param value
     * @param keyword
     * @param offset
     * @return
     */
    public static int countContinuedFromEnd(String value, char keyword, int offset){
        if (isEmpty(value)) {
            return 0;
        }
        char prev;
        char current;
        int count = 0;
        int index = value.lastIndexOf(keyword, offset);
        if(index > -1){
            prev = value.charAt(index);
            index--;
            if(index < 0){
                return 1;
            }
            current = value.charAt(index);
            if(prev != current){
                return 1;
            }
            count++;
            while(true){
                prev    = current;
                current = value.charAt(index);
                if(prev != current){
                    break;
                }else{
                    count++;
                    index--;
                    if(index < 0){
                        break;
                    }
                }
            }
        }
        return count;
    }


    /**
     * <pre>
     * 対象文字列の末尾(index = target.length() - 1)から、
     * 任意の検索文字が連続して前方に向かって何個含まれているかを返す。
     * 連続した文字ブロックは初めに発見したブロックのみ認識される。
     * 大文字小文字は区別される。
     *
     *
     * Example:
     *   StrUtil.countContinuedFromEnd(""          , 'A') = 0
     *   StrUtil.countContinuedFromEnd("-"         , 'A') = 0
     *   StrUtil.countContinuedFromEnd("---A"      , 'A') = 1
     *   StrUtil.countContinuedFromEnd("A---"      , 'A') = 1
     *   StrUtil.countContinuedFromEnd("AA---"     , 'A') = 2
     *   StrUtil.countContinuedFromEnd("AAA---AA"  , 'A') = 2
     *   StrUtil.countContinuedFromEnd("---AA"     , 'A') = 2
     *   StrUtil.countContinuedFromEnd("AAAA-AA---", 'A') = 2
     *   StrUtil.countContinuedFromEnd("AAA---BBBB", 'A') = 3
     *   StrUtil.countContinuedFromEnd("AAA---BBBB", 'B') = 4
     *   StrUtil.countContinuedFromEnd("BBB---BBBB", 'B') = 4
     *   StrUtil.countContinuedFromEnd(null        , 'A') = 0
     *
     * </pre>
     * @param value
     * @param keyword
     * @return
     */
    public static int countContinuedFromEnd(String value, char keyword){
        if(isEmpty(value)){
            return 0;
        }
        return countContinuedFromEnd(value, keyword, value.length() - 1);
    }


    /**
     * <pre>
     * 文字列配列中の、カラの要素数を返す。
     * カラの要素は、String#length()=0か、nullとする。
     *
     * Example:
     *   StrUtil.countEmpty(new String[]{"", "", "", ""})            = 4
     *   StrUtil.countEmpty(new String[]{"", "aaa", "", ""})         = 3
     *   StrUtil.countEmpty(new String[]{"", "111", "aaa", ""})      = 2
     *   StrUtil.countEmpty(new String[]{"000", "", "222", "333"})   = 1
     *   StrUtil.countEmpty(new String[]{"1", "2", "3", "4"})        = 0
     *
     * </pre>
     * @param values カウントしたいカラの要素が含まれる文字列配列
     * @return カラの要素数
     */
    public static int countEmpty(String[] values){
        int count = 0;
        for(int i=0; i<values.length;i++){
            if(isEmpty(values[i])){
                count++;
            }
        }
        return count;
    }


    /**
     * <pre>
     * 与えられた１文字が、文字配列中にあるかどうか。
     * アルファベットの場合、大文字小文字は区別される。
     *
     * Example:
     *   char [] chars = {'1','a','A', '#'};
     *   StrUtil.contains(chars, '1') = true
     *   StrUtil.contains(chars, 'a') = true
     *   StrUtil.contains(chars, 'A') = true
     *   StrUtil.contains(chars, '#') = true
     *   StrUtil.contains(chars, '2') = false
     *   StrUtil.contains(chars, '*') = false
     *
     * </pre>
     *
     * @param list 調査対象となる文字配列
     * @param c 調べたい１文字
     * @return 文字配列に調べたい１文字があればtrueを返す。それ以外はfalseを返す。
     */
    public static boolean contains(char[] list, char c){
        return contains(list, c, 0);
    }


    /**
     * <pre>
     * 与えられた１文字が、文字配列中にあるかどうか。
     * アルファベットの場合、大文字小文字は区別される。
     *
     * Example:
     *   char [] cs = {'1','a','A', '#'};
     *   StrUtil.contains(cs, '1', 0) = true
     *   StrUtil.contains(cs, '1', 1) = false
     *   StrUtil.contains(cs, '1', 2) = false
     *   StrUtil.contains(cs, '1', 3) = false
     *   StrUtil.contains(cs, 'a', 0) = true
     *   StrUtil.contains(cs, 'a', 1) = true
     *   StrUtil.contains(cs, 'a', 2) = false
     *   StrUtil.contains(cs, 'a', 3) = false
     *   StrUtil.contains(cs, 'A', 0) = true
     *   StrUtil.contains(cs, 'A', 1) = true
     *   StrUtil.contains(cs, 'A', 2) = true
     *   StrUtil.contains(cs, 'A', 3) = false
     *   StrUtil.contains(cs, '#', 0) = true
     *   StrUtil.contains(cs, '#', 1) = true
     *   StrUtil.contains(cs, '#', 2) = true
     *   StrUtil.contains(cs, '#', 3) = true
     *   StrUtil.contains(cs, '2', 0) = false
     *   StrUtil.contains(cs, '*', 0) = false
     *
     * </pre>
     * @param list 調査対象となる文字配列
     * @param c 調べたい１文字
     * @param offset 文字配列中の比較を開始したいインデックス
     * @return 文字配列に調べたい１文字があればtrueを返す。それ以外はfalseを返す。
     */
    public static boolean contains(char[] list, char c, int offset){
        if(isEmpty(list)){
            return false;
        }
        for(int i = offset; i < list.length; i++){
            if(c == list[i]){
                return true;
            }
        }
        return false;
    }


    /**
     * <pre>
     * 与えられた１語が、文字列配列中にあるかどうか。
     *
     * Example:
     *   String[] list = {"Apple", "Orange", "Banana"};
     *   String[] nulls = null;
     *   StrUtil.contains(list,  "apple",  true ) = true
     *   StrUtil.contains(list,  "ORANGE", true ) = true
     *   StrUtil.contains(list,  "BaNaNa", true ) = true
     *   StrUtil.contains(list,  "Apple",  false) = true
     *   StrUtil.contains(list,  "Orange", false) = true
     *   StrUtil.contains(list,  "Banana", false) = true
     *   StrUtil.contains(list,  "apple",  false) = false
     *   StrUtil.contains(list,  "ORANGE", false) = false
     *   StrUtil.contains(list,  "BaNaNa", false) = false
     *   StrUtil.contains(list,  "Peach",  true ) = false
     *   StrUtil.contains(list,  null,     true ) = false
     *   StrUtil.contains(nulls, "apple",  true ) = false
     *
     * </pre>
     *
     * @param list 調査対象となる文字列配列
     * @param keyword 調べたい１語
     * @param ignoreCase 大文字小文字を無視するならtrue
     * @return 文字列配列に調べたい１語があればtrueを返す。それ以外はfalseを返す。
     */
    public static boolean contains(String[] list, String keyword, boolean ignoreCase){
        if(isEmpty(keyword) || isEmpty(list)){
            return false;
        }
        String chkWord = keyword;
        if(ignoreCase){
            chkWord = chkWord.toLowerCase();
        }
        for(int i=0; i<list.length; i++){
            String chkInList = list[i];
            if(ignoreCase){
                chkInList = chkInList.toLowerCase();
            }
            if( chkWord.equals( chkInList )){
                return true;
            }
        }
        return false;
    }


    /**
     * <pre>
     * 与えられた文字列のstartIndexで示される位置からmatchLengthで指定された文字数分が、
     * 文字列配列中にあるかどうか。
     *
     * Example:
     *   String[] list = { "Apple", "Orange", "Banana", "Pie" };
     *   String[] nulls = null;
     *
     *   StrUtil.contains(list, "ApplePieWorld", 0, 5,  true ) = true
     *   StrUtil.contains(list, "ApplePieWorld", 1, 5,  true ) = false
     *   StrUtil.contains(list, "ApplePieWorld", 0, 6,  true ) = false
     *   StrUtil.contains(list, "applepieworld", 0, 5,  false) = false
     *   StrUtil.contains(list, "ApplePieWorld", 5, 3,  true ) = true
     *   StrUtil.contains(list, "ApplePieWorld", 6, 3,  true ) = false
     *   StrUtil.contains(list, "ApplePieWorld", 5, 4,  true ) = false
     *   StrUtil.contains(list, "applepieworld", 5, 3,  false) = false
     *   StrUtil.contains(null, "applepieworld", 5, 3,  false) = false
     *   StrUtil.contains(list,  null          , 5, 3,  false) = false
     *   StrUtil.contains(null,  null          , 5, 3,  false) = false
     *
     * </pre>
     * @param list 調査対象となる文字列配列
     * @param keyword 調べたい文字列
     * @param offset value中の比較開始位置
     * @param matchLength 比較文字数
     * @param ignoreCase 大文字小文字を無視するならtrue
     * @return valueの指定位置から指定文字数分の文字列がwordList中にあればtrueを返す。それ以外はfalseを返す。
     */
    public static boolean contains(String[] list, String keyword, int offset, int matchLength, boolean ignoreCase) {
        if(isEmpty(keyword)){
            return false;
        }
        if(offset > keyword.length()){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t startIndex = " + offset + ": but value.length() = " + keyword.length()+ "."
                    );
        }
        int endIndex = offset + matchLength;
        if(endIndex > keyword.length()){
            return false;
        }
        String word = keyword.substring(offset, endIndex);
        return contains(list, word, ignoreCase);
    }


    /**
     * <pre>
     * 対象文字列に、空白が含まれているかどうか調べる。
     * 空白は、半角スペース、タブ、改行コード。
     *
     * Example:
     *   StrUtil.containsAnyBlank("123 ABC")    = true
     *   StrUtil.containsAnyBlank("123\tABC")   = true
     *   StrUtil.containsAnyBlank("123ABC\r")   = true
     *   StrUtil.containsAnyBlank("\n123ABC")   = true
     *   StrUtil.containsAnyBlank("123ABC")     = false
     *   StrUtil.containsAnyBlank("\"123ABC\"") = false
     * </pre>
     *
     * @param value
     * @return 空白が含まれていたらtrue
     */
    public static boolean containsAnyBlank(String value){
        if(isEmpty(value)){
            return true;
        }
        char[] array = value.toCharArray();
        boolean rtn = false;
        for(int i=0; i<array.length; i++){
            if(isBlankAny(array[i])){
                rtn = true;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * 引数１と引数２を比較する際に、アルファベットの大文字小文字を問わない。
     * 全角アルファベットでも大文字小文字は問わない。
     * 全角アルファベットと半角アルファベットは異なる。
     *
     * Example:
     *   StrUtil.equalsIgnoreCase("123ABC", "123abc")         = true
     *   StrUtil.equalsIgnoreCase("123ABC", "123ABC")         = true
     *   StrUtil.equalsIgnoreCase("123ABC", "123abcd")        = false
     *   StrUtil.equalsIgnoreCase("123ＡＢＣ", "123ａｂｃ")   = true
     *   StrUtil.equalsIgnoreCase("123ＡＢＣ", "123ＡＢＣ")   = true
     *   StrUtil.equalsIgnoreCase("123ＡＢＣ", "123ａｂｃｄ") = false
     *   StrUtil.equalsIgnoreCase("123ABC", "123ＡＢＣ")      = false
     *
     * </pre>
     * @param value1
     * @param value2
     * @return 比較結果のtrue/false
     */
    public static boolean equalsIgnoreCase(String value1, String value2){
        return(value1.toLowerCase().equals( value2.toLowerCase() ));
    }


    /**
     * <pre>
     * 与えられた1文字が、改行コード(CR)かどうか。
     *
     * Example:
     *   StrUtil.isCR('\r') = true
     *   StrUtil.isCR('\n') = false
     *   StrUtil.isCR('\t') = false
     *   StrUtil.isCR(' ')  = false
     *
     * </pre>
     * @param c 判定文字
     * @return 改行コード(CR)ならtrue、そうでなければfalse
     */
    public static boolean isCR(char c){
        return (c == CR);
    }


    /**
     * <pre>
     * 与えられた1文字が、改行コード(LF)かどうか。
     *
     * Example:
     *   StrUtil.isLF('\n') = true
     *   StrUtil.isLF('\r') = false
     *   StrUtil.isLF('\t') = false
     *   StrUtil.isLF(' ')  = false
     *
     * </pre>
     * @param c 判定文字
     * @return 改行コード(CR)ならtrue、そうでなければfalse
     */
    public static boolean isLF(char c){
        return (c == LF);
    }


    /**
     * <pre>
     * 与えられた1文字が、改行コード(CR or LF)かどうか。
     *
     * Example:
     *   StrUtil.isCRLF('\r') = true
     *   StrUtil.isCRLF('\n') = true
     *   StrUtil.isCRLF('\t') = true
     *   StrUtil.isCRLF('0')  = false
     *   StrUtil.isCRLF('9')  = false
     *   StrUtil.isCRLF('A')  = false
     *   StrUtil.isCRLF('Z')  = false
     *   StrUtil.isCRLF('a')  = false
     *   StrUtil.isCRLF('z')  = false
     *
     * </pre>
     * @param c 判定文字
     * @return 改行コード(CR or LF)ならtrue、そうでなければfalse
     */
    public static boolean isCRLF(char c){
        return (c == CR || c == LF);
    }


    /**
     * <pre>
     * 与えられた文字列が半角数字かどうか。
     *
     * Example:
     *   StrUtil.isNumber("1234567890") = true
     *   StrUtil.isNumber("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") = false
     *   StrUtil.isNumber("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") = false
     *
     * </pre>
     * @param value
     * @return
     */
    public static boolean isNumber(String value) {
        boolean rtn = true;
        for (int i=0;i < value.length();i++) {
            if ( !isNumber(value.charAt(i)) ) {
                rtn = false;
                break;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * 与えられた１文字が半角数字かどうか。
     *
     * Example:
     *     StrUtil.isNumber('0') = true
     *     StrUtil.isNumber('9') = true
     *     StrUtil.isNumber('A') = false
     *     StrUtil.isNumber('Z') = false
     *     StrUtil.isNumber('a') = false
     *     StrUtil.isNumber('z') = false
     *
     * </pre>
     * @param c
     * @return
     */
    public static boolean isNumber(char c){
        return( (c < '0' || c > '9') ? false : true );
    }


    /**
     * <pre>
     * 与えられた文字列が半角アルファベットかどうか。
     *
     * Example:
     *   StrUtil.isNumber("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") = true
     *   StrUtil.isNumber("1234567890") = false
     *   StrUtil.isNumber("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") = false
     *
     * </pre>
     * @param value
     * @return
     */
    public static boolean isAlphabet(String value) {
        boolean rtn = true;
        for (int i=0;i < value.length();i++) {
            if (!isAlphabet(value.charAt(i)))
            {
                rtn = false;
                break;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * 与えられた１文字が半角アルファベットかどうか。
     *
     * Example:
     *     StrUtil.isAlphabet('A') = true
     *     StrUtil.isAlphabet('Z') = true
     *     StrUtil.isAlphabet('a') = true
     *     StrUtil.isAlphabet('z') = true
     *     StrUtil.isAlphabet('0') = false
     *     StrUtil.isAlphabet('9') = false
     *
     * </pre>
     * @param c
     * @return
     */
    public static boolean isAlphabet(char c){
        return((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') ? false : true);
    }


    /**
     * <pre>
     * 与えられた１語が、半角アルファベット又は数字かを調べる。
     *
     * Example:
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")    = true
     *   StrUtil.isNumAlpha("81-03-1234-5678")                                                    = false
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_")  = false
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_ ") = false
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_!") = false
     * </pre>
     *
     * @param value
     * @return '0'-'9'、'A'-'Z'、'a'-'z'ならtrueを返す
     */
    public static boolean isNumAlpha(String value) {
        boolean rtn = true;
        for (int i=0;i < value.length();i++) {
            if (!isNumAlpha(value.charAt(i))) {
                rtn = false;
                break;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * 与えられた１文字が、半角アルファベット又は数字かを調べる。
     *
     * Example:
     *   StrUtil.isNumAlpha('0') = true
     *   StrUtil.isNumAlpha('9') = true
     *   StrUtil.isNumAlpha('A') = true
     *   StrUtil.isNumAlpha('Z') = true
     *   StrUtil.isNumAlpha('a') = true
     *   StrUtil.isNumAlpha('z') = true
     *   StrUtil.isNumAlpha('_') = false
     *   StrUtil.isNumAlpha('-') = false
     *   StrUtil.isNumAlpha(' ') = false
     *   StrUtil.isNumAlpha('!') = false
     *   StrUtil.isNumAlpha('#') = false
     *   StrUtil.isNumAlpha('０') = false
     *   StrUtil.isNumAlpha('９') = false
     *   StrUtil.isNumAlpha('Ａ') = false
     *   StrUtil.isNumAlpha('Ｚ') = false
     *   StrUtil.isNumAlpha('ａ') = false
     *   StrUtil.isNumAlpha('ｚ') = false
     *   StrUtil.isNumAlpha('－') = false
     *   StrUtil.isNumAlpha('＿') = false
     *
     * </pre>
     *
     * @param c
     * @return '0'-'9'、'A'-'Z'、'a'-'z'ならtrueを返す
     */
    public static boolean isNumAlpha(char c){
        return( !(isAlphabet(c) || isNumber(c)) ? false : true );
    }


    /**
     * <pre>
     * 与えられた１語が、半角アルファベット又は数字か、バー('-', '_')かを調べる。
     *
     * Example:
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")    = true
     *   StrUtil.isNumAlpha("81-03-1234-5678")                                                    = true
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_")  = true
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_ ") = false
     *   StrUtil.isNumAlpha("01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_!") = false
     * </pre>
     *
     * @param value
     * @return '0'-'9'、'A'-'Z'、'a'-'z'、'-'、'_'ならtrueを返す
     */
    public static boolean isNumAlphaBar(String value) {
        boolean rtn = true;
        char c;
        for (int i=0;i < value.length();i++) {
            c = value.charAt(i);
            if ( !isNumAlpha(c)
                  && (c != '-')
                  && (c != '_')
                )
            {
                rtn = false;
                break;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * 与えられた１文字が、半角アルファベット又は数字か、バー('-', '_')かを調べる。
     *
     * Example:
     *   StrUtil.isNumAlphaBar('0') = true
     *   StrUtil.isNumAlphaBar('9') = true
     *   StrUtil.isNumAlphaBar('A') = true
     *   StrUtil.isNumAlphaBar('Z') = true
     *   StrUtil.isNumAlphaBar('a') = true
     *   StrUtil.isNumAlphaBar('z') = true
     *   StrUtil.isNumAlphaBar('_') = true
     *   StrUtil.isNumAlphaBar('-') = true
     *   StrUtil.isNumAlphaBar(' ') = false
     *   StrUtil.isNumAlphaBar('!') = false
     *   StrUtil.isNumAlphaBar('#') = false
     *   StrUtil.isNumAlphaBar('０') = false
     *   StrUtil.isNumAlphaBar('９') = false
     *   StrUtil.isNumAlphaBar('Ａ') = false
     *   StrUtil.isNumAlphaBar('Ｚ') = false
     *   StrUtil.isNumAlphaBar('ａ') = false
     *   StrUtil.isNumAlphaBar('ｚ') = false
     *   StrUtil.isNumAlphaBar('－') = false
     *   StrUtil.isNumAlphaBar('＿') = false
     *
     * </pre>
     *
     * @param c
     * @return '0'-'9'、'A'-'Z'、'a'-'z'、'-'、'_'ならtrueを返す
     */
    public static boolean isNumAlphaBar(char c){
        return( (!isNumAlpha(c) && (c != '-') && (c != '_')) ? false : true );
    }


    /**
     * <pre>
     * 与えられた１文字が、URLを構成する文字として有効どうか。
     *
     * RFC2396 "Uniform Resource Identifiers (URI): Generic Syntax"
     * uric = reserved | unreserved | escaped
     * (uric = URI Characters)
     *
     * reserved   = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
     * unreserved = alphanum | mark
     * mark       = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
     * escaped    = "%" hex hex
     *
     * </pre>
     * @param c 判定したい文字
     * @return 有効な文字ならtrue、無効な文字ならfalseを返す
     */
    public static boolean isElementOfURL(char c){
        return (isNumAlpha(c) || contains(URIC, c)) ? true : false;
    }

    /**
     * <pre>
     * 与えられた１語が、「URLとして有効な文字で構成されている」かどうか。
     * 「URLとして有効かどうか」は判定しない。
     *
     * Example:
     *   StrUtil.isElementOfURL("http://www.example.org/foo/bar.cgi?key=val&q=%81%82%83%84") = true
     *   StrUtil.isElementOfURL("09AZaz;/?:@&=+$,-_.!~*'()%") = true
     *   StrUtil.isElementOfURL(" \t\r\n\"#|`<>[]{}")         = false
     *
     * @param value 判定したい一語
     * @return URLとして有効な文字で構成されていればtrue、無効ならfalse
     */
    public static boolean isElementOfURL(String value){
        boolean rtn = true;
        for (int i=0;i < value.length();i++) {
            if (!isElementOfURL(value.charAt(i))) {
                rtn = false;
                break;
            }
        }
        return rtn;

    }
    /**
     * <pre>
     * 与えられた1文字が、半角スペースまたはタブか。
     *
     * Example:
     *   StrUtil.isSpaceOrTab(' ')  = true
     *   StrUtil.isSpaceOrTab('\t') = true
     *   StrUtil.isSpaceOrTab('0')  = false
     *   StrUtil.isSpaceOrTab('9')  = false
     *   StrUtil.isSpaceOrTab('A')  = false
     *   StrUtil.isSpaceOrTab('Z')  = false
     *   StrUtil.isSpaceOrTab('a')  = false
     *   StrUtil.isSpaceOrTab('z')  = false
     *
     * </pre>
     * @param c
     * @return
     */
    public static boolean isSpaceOrTab(char c){
        return (c == SPC || c == TAB);
    }


    /**
     * <pre>
     * 与えられた１文字が空白かどうか。
     * 空白と判定される文字は、半角スペース、タブ、改行コード(\r, \n)となる。
     *
     * Example:
     *   StrUtil.isBlankAny(' ')  = true
     *   StrUtil.isBlankAny('\t') = true
     *   StrUtil.isBlankAny('\r') = true
     *   StrUtil.isBlankAny('\n') = true
     *   StrUtil.isBlankAny('1')  = false
     *   StrUtil.isBlankAny('A')  = false
     *   StrUtil.isBlankAny('!')  = false
     *
     * </pre>
     *
     * @param c
     * @return 空白ならtrue、それ以外はfalse
     */
    public static boolean isBlankAny(char c){
        char[] cs = { SPC, TAB, LF, CR };
        return contains(cs, c);
    }


    /**
     * <pre>
     * 与えられた文字列中の任意の位置の１文字が空白かどうか。
     * 空白と判定される文字は、半角スペース、タブ、改行コード(\r, \n)となる。
     *
     * Example:
     *   StrUtil.isBlankCharAt(0, " #ABC")  = true
     *   StrUtil.isBlankCharAt(1, "# ABC")  = true
     *   StrUtil.isBlankCharAt(2, "#A\tBC") = true
     *   StrUtil.isBlankCharAt(3, "#AB\rC") = true
     *   StrUtil.isBlankCharAt(4, "#ABC\n") = true
     *   StrUtil.isBlankCharAt(0, "##ABC")  = false
     *   StrUtil.isBlankCharAt(4, "##ABC")  = false
     *   StrUtil.isBlankCharAt(5, "#ABC\n") = java.lang.StringIndexOutOfBoundsException
     *
     * </pre>
     *
     * @param index
     * @param value
     * @return 文字列中の任意の位置の１文字が空白ならtrue
     */
    public static boolean isBlankCharAt(int index, String value){
        char c = value.charAt(index);
        return isBlankAny(c);
    }


    /**
     * <pre>
     * 与えられた１文字が括弧かどうか。
     * 括弧と判定される文字は、(、)、[、]、{、}、の６文字。
     *
     * Example:
     *   StrUtil.isBracket('(') = true
     *   StrUtil.isBracket(')') = true
     *   StrUtil.isBracket('[') = true
     *   StrUtil.isBracket(']') = true
     *   StrUtil.isBracket('{') = true
     *   StrUtil.isBracket('}') = true
     *   StrUtil.isBracket('1') = false
     *   StrUtil.isBracket('A') = false
     *   StrUtil.isBracket('!') = false
     *   StrUtil.isBracket(' ') = false
     *
     * </pre>
     *
     * @param c
     * @return 文字列中の任意の位置の１文字が空白ならtrue
     */
    public static boolean isBracket(char c) {
        char[] chars = { '(', ')', '[', ']', '{', '}' };
        return contains(chars, c);
    }


    /**
     * <pre>
     * 与えられた１文字が括弧の開始かどうか。
     * 括弧の開始と判定される文字は、(、[、{、の３文字。
     *
     * Example:
     *   StrUtil.isBracketOpen('(') = true
     *   StrUtil.isBracketOpen('[') = true
     *   StrUtil.isBracketOpen('{') = true
     *   StrUtil.isBracketOpen(')') = false
     *   StrUtil.isBracketOpen(']') = false
     *   StrUtil.isBracketOpen('}') = false
     * </pre>
     * @param c
     * @return 括弧の開始ならtrue
     */
    public static boolean isBracketOpen(char c){
        char [] cs = {'(','[','{'};
        return contains(cs, c);
    }


    /**
     * <pre>
     * 与えられた１文字が括弧の終了かどうか。
     * 括弧の終了と判定される文字は、)、]、}、の３文字。
     *
     * Example:
     *   StrUtil.isBracketClose(')') = true
     *   StrUtil.isBracketClose(']') = true
     *   StrUtil.isBracketClose('}') = true
     *   StrUtil.isBracketClose('(') = false
     *   StrUtil.isBracketClose('[') = false
     *   StrUtil.isBracketClose('{') = false
     * </pre>
     * @param c
     * @return 括弧の終了ならtrue
     */
    public static boolean isBracketClose(char c){
        char [] cs = {')',']','}'};
        return contains(cs, c);
    }


    /**
     * <pre>
     * インデックスが与えられた文字列のインデックスの範囲内かどうか。
     *
     * Example:
     *   StrUtil.isCorrectIndex("0123", -1) = false
     *   StrUtil.isCorrectIndex("0123",  0) = true
     *   StrUtil.isCorrectIndex("0123",  3) = true
     *   StrUtil.isCorrectIndex("0123",  4) = false
     *
     * </pre>
     * @param value 文字列
     * @param offset インデックス
     * @return
     */
    public static boolean isCorrectIndex(String value, int offset){
        return ( !isEmpty(value) && offset > -1  && offset < value.length() );
    }


    /**
     * <pre>
     * 文字列がnullか空かを判断する。
     *
     * Example:
     *   String value = null;
     *   StrUtil.isEmpty(value) = true
     *   StrUtil.isEmpty("")   = true
     *   StrUtil.isEmpty("a")  = false
     *   StrUtil.isEmpty("1")  = false
     *   StrUtil.isEmpty(" ")  = false
     *
     * </pre>
     * @param value
     * @return 文字列がnullか空ならtrueを返す
     */
    public static boolean isEmpty(String value) {
        return (value == null || value.length() == 0);
    }


    /**
     * <pre>
     * Object型配列がnullか空かを判断する。
     *
     * Example:
     *   Object[] arrayNull = null;
     *   Object[] arrays0 = new Object[0];
     *   Object[] arrays1 = new Object[1];
     *   StrUtil.isEmpty(arrayNull) = true
     *   StrUtil.isEmpty(arrays0)   = true
     *   StrUtil.isEmpty(arrays1)   = false
     *
     * </pre>
     * @param objects
     * @return
     */
    public static boolean isEmpty(Object[] objects) {
        return (objects == null || objects.length == 0);
    }


    /**
     * <pre>
     * char型配列がnullか空かを判断する。
     *
     * </pre>
     * @param chars
     * @return
     */
    public static boolean isEmpty(char[] chars){
        return (chars == null || chars.length == 0);
    }

    /**
     * <pre>
     * int型配列がnullか空かを判断する。
     *
     * </pre>
     * @param chars
     * @return
     */
    public static boolean isEmpty(int[] ints){
        return (ints == null || ints.length == 0);
    }


    /**
     * <pre>
     * 文字列が、指定したencloserで囲まれているかどうかを判定する。
     *
     * Example:
     *   StrUtil.isEnclosed("\"a enclosed string\"", '"') = true
     *   StrUtil.isEnclosed("\"a enclosed string\"", '"') = true
     *   StrUtil.isEnclosed("'a enclosed string", '\'')   = false
     *   StrUtil.isEnclosed(null, '"')                    = false
     * </pre>
     *
     * @param value クォートされているかどうか調べたい文字列
     * @param encloser クォート文字
     * @return クォートされていればtrue、それ以外はfalse
     */
    public static boolean isEnclosed(String value, char encloser){
        return ( ! isEmpty(value)
                && value.charAt(0) == encloser
                && value.charAt(value.length()-1) == encloser);
    }


    /**
     * <pre>
     * 文字列が、指定したencloserで囲まれているかどうかを判定する。
     *
     * Example:
     *   StrUtil.isEnclosed("<DATA>", '<', '>')   = true
     *   StrUtil.isEnclosed("(DATA)", '(', ')')   = true
     *   StrUtil.isEnclosed("<DATA>", '[', ']')   = false
     *   StrUtil.isEnclosed(" <DATA> ", '<', '>') = false
     *
     * </pre>
     * @param value 対象文字列
     * @param openEncloser 開始encloser
     * @param closeEncloser 閉じencloser
     * @return
     */
    public static boolean isEnclosed(String value, char openEncloser, char closeEncloser){
        return (!isEmpty(value) && value.charAt(0) == openEncloser && value.charAt(value.length()-1) == closeEncloser);
    }


    /**
     * <pre>
     * 文字列中、インデックスで示される位置の文字が、
     * その直前にエスケープ文字('\','%'等)で修飾されたものかどうか。
     *
     * Example:
     *   StrUtil.isEscaped("123\\[AAA\\]456", 0,   '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 3,   '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 4,   '\\') = true
     *   StrUtil.isEscaped("123\\[AAA\\]456", 5,   '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 8,   '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 9,   '\\') = true
     *   StrUtil.isEscaped("123\\[AAA\\]456", 12,  '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 13,  '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 14,  '\\') = false
     *   StrUtil.isEscaped("123\\[AAA\\]456", 999, '\\') = false
     *
     * </pre>
     * @param value 対象文字列
     * @param offset オフセット値
     * @param escapeChar エスケープ文字
     * @return
     */
    public static boolean isEscaped(String value, int offset, char escapeChar){
        return (isCorrectIndex(value, offset) && offset > 0 && value.charAt(offset - 1) == escapeChar);
    }


    /**
     * <pre>
     * 与えられた文字列が、連続した半角スペースまたはタブか。
     *
     * Example:
     *   StrUtil.isContinuedSpacesOrTabs("  ")     = true
     *   StrUtil.isContinuedSpacesOrTabs("\t")     = true
     *   StrUtil.isContinuedSpacesOrTabs(" \t \t") = true
     *   StrUtil.isContinuedSpacesOrTabs(" A ")    = false
     *
     * </pre>
     * @param value
     * @return
     */
    public static boolean isContinuedSpacesOrTabs(String value){
        boolean result = true;
        for(int i = 0; i < value.length(); i++){
            if(!isSpaceOrTab(value.charAt(i))){
                result = false;
                break;
            }
        }
        return result;
    }


    /**
     * <pre>
     * java.lang.String#indexOf()の大文字小文字を問わない版。
     *
     * Example:
     *   StrUtil.indexOfIgnoreCase("apple", "Apple Computer, Inc.")     = 0
     *   StrUtil.indexOfIgnoreCase("CoMpUtEr", "Apple Computer, Inc.")  = 6
     *   StrUtil.indexOfIgnoreCase("CoMpUtErS", "Apple Computer, Inc.") = -1
     *   StrUtil.indexOfIgnoreCase("Orange", "Apple Computer, Inc.")    = -1
     * </pre>
     *
     * @param keyword
     * @param value
     * @return 引数２の中に引数１があれば、そのインデックス
     */
    public static int indexOfIgnoreCase(String keyword, String value){
        return ( value.toLowerCase().indexOf(keyword.toLowerCase()) );
    }


    /**
     * <pre>
     * 文字列中の指定位置以降の空白、タブ、改行コードをスキップし、
     * 最初の文字の位置を返す。
     * 文字列がすべて空白なら -1 を返す。
     *
     * Example:
     *   StrUtil.indexOfNextChar("ABC"            , 0) = 0
     *   StrUtil.indexOfNextChar(" ABC"           , 0) = 1
     *   StrUtil.indexOfNextChar("\tABC"          , 0) = 1
     *   StrUtil.indexOfNextChar("\rABC"          , 0) = 1
     *   StrUtil.indexOfNextChar("\nABC"          , 0) = 1
     *   StrUtil.indexOfNextChar(" \t\rABC"       , 0) = 3
     *   StrUtil.indexOfNextChar(" \t \r \n012ABC", 0) = 6
     *   StrUtil.indexOfNextChar("123 \t ABC"     , 0) = 0
     *   StrUtil.indexOfNextChar("123 \t ABC"     , 2) = 2
     *   StrUtil.indexOfNextChar("123 \t ABC"     , 3) = 6
     *   StrUtil.indexOfNextChar("123 \t ABC"     , 4) = 6
     *   StrUtil.indexOfNextChar(" \t "           , 0) = -1
     *   StrUtil.indexOfNextChar(""               , 0) = -1
     *   StrUtil.indexOfNextChar(null             , 0) = -1
     *
     * </pre>
     * @param value
     * @return startPos 次の文字列開始位置のインデックス
     */
    public static int indexOfNextChar(String value, int offset){
        if(isEmpty(value)){
            return -1;
        }

        int pos = offset;
        char c;
        while(pos < value.length()){
            c = value.charAt(pos);
            if(c==SPC || c==TAB || c==LF || c==CR){
                pos++;
            }else{
                break;
            }
        }
        if(pos == value.length()){
            pos = -1;
        }
        return pos;
    }

    /**
     * <pre>
     * 文字列中末尾から先頭に向かって、空白、タブ、改行コードをスキップし、
     * 最初の文字の位置を返す。
     * 文字列がすべて空白なら -1 を返す。
     * 第二引数で指定されるオフセット値は、検索開始位置となる
     * 文字列末尾のインデックスから引かれる値。'0'なら文字列末尾、
     * '1'なら文字列末尾から1文字前、'2'なら文字列末尾から2文字前から検索開始となる。
     *
     * Example:
     *   StrUtil.lastIndexOfPrevChar("    AB    CD    EF",  0) = 17
     *   StrUtil.lastIndexOfPrevChar("    AB    CD    EF",  2) = 11
     *   StrUtil.lastIndexOfPrevChar("    AB    CD    E ",  0) = 16
     *   StrUtil.lastIndexOfPrevChar("    AB    CD    E ",  8) =  5
     *   StrUtil.lastIndexOfPrevChar("    AB    CD      ",  0) = 11
     *   StrUtil.lastIndexOfPrevChar("    AB            ",  0) =  5
     *   StrUtil.lastIndexOfPrevChar(" 0                ",  0) =  1
     *   StrUtil.lastIndexOfPrevChar("0                 ",  0) =  0
     *   StrUtil.lastIndexOfPrevChar("   \t        \t   ",  0) = -1
     *   StrUtil.lastIndexOfPrevChar("",                    0) = -1
     *   StrUtil.lastIndexOfPrevChar(null,                  0) = -1
     *   StrUtil.lastIndexOfPrevChar("    AB            ", 20) = -1
     *
     * </pre>
     * @param value
     * @param endOffset
     * @return
     */
    public static int lastIndexOfPrevChar(String value, int endOffset){
        if(isEmpty(value)){
            return -1;
        }
        int pos = value.length() - 1 - endOffset;
        if(pos < 0){
            return -1;
        }
        char c = 0;
        while(pos > 0){
            c = value.charAt(pos);
            if(c==SPC || c==TAB || c==LF || c==CR){
                pos--;
            }else{
                break;
            }
        }
        c = value.charAt(pos);
        if(pos == 0 && (c==SPC || c==TAB || c==LF || c==CR)){
            pos--;
        }
        return pos;
    }


    /**
     * <pre>
     * 文字列中の指定位置以降の文字をスキップし、
     * 最初に発見した半角スペース、タブ、改行コードを返す。
     * 文字列末尾まで半角スペース、タブ、改行コードを
     * 見つけられなかった場合、-1 を返す。
     *
     * Example:
     *   StrUtil.indexOfNextBlank("ABC  "         , 0) =  3
     *   StrUtil.indexOfNextBlank("ABCD\t  "      , 1) =  4
     *   StrUtil.indexOfNextBlank("ABCD\r  "      , 2) =  4
     *   StrUtil.indexOfNextBlank("ABCD\n  "      , 3) =  4
     *   StrUtil.indexOfNextBlank("ABCD 1234   1" , 5) =  9
     *   StrUtil.indexOfNextBlank("ABCD 1234"     , 5) = -1
     *   StrUtil.indexOfNextBlank("ABCD"          , 2) = -1
     *   StrUtil.indexOfNextBlank("ABCD"          , 6) = -1
     *   StrUtil.indexOfNextBlank(""              , 1) = -1
     *   StrUtil.indexOfNextBlank(null            , 0) = -1
     *
     * </pre>
     * @param value
     * @param offset
     * @return 次の空白のインデックス
     */
    public static int indexOfNextBlank(String value, int offset){
        if(isEmpty(value)){
            return -1;
        }
        int pos = offset;
        char c;
        while(pos < value.length()){
            c = value.charAt(pos);
            if(c!=SPC && c!=TAB && c!=LF && c!=CR){
                pos++;
            }else{
                break;
            }
        }
        if(pos >= value.length()){
            pos = -1;
        }
        return pos;
    }


    /**
     * <pre>
     * 文字列中末尾から先頭に向かって空白以外の文字をスキップし、最初の空白文字の位置を返す。
     * 検索する空白文字は、半角スペース、タブ、改行コード。
     * 第二引数で指定されるオフセット値は、検索開始位置となる文字列末尾のインデックスから引かれる値。
     * '0'なら文字列末尾、'1'なら文字列末尾から1文字前、'2'なら文字列末尾から2文字前から検索開始となる。
     * 前方の空白を文字列先頭まで見つけられなかった場合、-1を返す。
     *
     * Example:
     *   StrUtil.lastIndexOfPrevBlank("    AB    CD    EF",  0) = 15
     *   StrUtil.lastIndexOfPrevBlank("    AB    CD      ",  0) = 17
     *   StrUtil.lastIndexOfPrevBlank("    AB    CD      ",  6) =  9
     *   StrUtil.lastIndexOfPrevBlank(" ** AB************",  0) =  3
     *   StrUtil.lastIndexOfPrevBlank(" ***AB            ", 12) =  0
     *   StrUtil.lastIndexOfPrevBlank("****AB            ", 12) = -1
     *   StrUtil.lastIndexOfPrevBlank("****AB            ", 30) = -1
     *   StrUtil.lastIndexOfPrevBlank("",                    0) = -1
     *   StrUtil.lastIndexOfPrevBlank(null,                  0) = -1
     *   StrUtil.lastIndexOfPrevBlank("ABCD\t",              0) =  4
     *   StrUtil.lastIndexOfPrevBlank("\nABCD",              0) =  0
     *
     * </pre>
     * @param value
     * @param endOffset
     * @return
     */
    public static int lastIndexOfPrevBlank(String value, int endOffset){
        if(isEmpty(value)){
            return -1;
        }
        int pos = value.length() - 1 - endOffset;
        if(pos < 0){
            return -1;
        }
        char c = 0;
        while(pos > 0){
            c = value.charAt(pos);
            if(c!=SPC && c!=TAB && c!=LF && c!=CR){
                pos--;
            }else{
                break;
            }
        }
        c = value.charAt(pos);
        if(pos == 0 && (c!=SPC && c!=TAB && c!=LF && c!=CR)){
            pos--;
        }
        return pos;
    }


    /**
     * <pre>
     * 文字列中の指定位置以降の文字をスキップし、
     * 最初に発見した改行コードを返す。
     * 文字列末尾まで改行コードを
     * 見つけられなかった場合、-1 を返す。
     *
     * Example:
     *   StrUtil.indexOfNextCRLF("ABC  1"      , 0) = -1
     *   StrUtil.indexOfNextCRLF("ABCD\t  1"   , 1) = -1
     *   StrUtil.indexOfNextCRLF("ABCD\r  1"   , 2) =  4
     *   StrUtil.indexOfNextCRLF("ABCD\n  1"   , 3) =  4
     *   StrUtil.indexOfNextCRLF("ABCD 1234 \n", 5) = 10
     *   StrUtil.indexOfNextCRLF("ABCD 1234 \r", 5) = 10
     *   StrUtil.indexOfNextCRLF("ABCD"        , 2) = -1
     *   StrUtil.indexOfNextCRLF("ABCD"        , 6) = -1
     *   StrUtil.indexOfNextCRLF(""            , 1) = -1
     *   StrUtil.indexOfNextCRLF(null          , 0) = -1
     *
     * </pre>
     * @param value
     * @param offset
     * @return
     */
    public static int indexOfNextCRLF(String value, int offset){
        if(isEmpty(value)){
            return -1;
        }
        int pos = offset;
        char c;
        while(pos < value.length()){
            c = value.charAt(pos);
            if(c!=LF && c!=CR){
                pos++;
            }else{
                break;
            }
        }
        if(pos >= value.length()){
            pos = -1;
        }
        return pos;
    }


    /**
     * <pre>
     * 文字列中末尾から先頭に向かって改行コード以外の文字をスキップし、最初の改行コードの位置を返す。
     * 第二引数で指定されるオフセット値は、検索開始位置となる文字列末尾のインデックスから引かれる値。
     * '0'なら文字列末尾、'1'なら文字列末尾から1文字前、'2'なら文字列末尾から2文字前から検索開始となる。
     * 前方の空白を文字列先頭まで見つけられなかった場合、-1を返す。
     *
     * Example:
     *   StrUtil.lastIndexOfPrevCRLF("ABC  1"      , 0) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD\t  1"   , 1) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD\r  1"   , 2) =  4
     *   StrUtil.lastIndexOfPrevCRLF("ABCD\n  1"   , 3) =  4
     *   StrUtil.lastIndexOfPrevCRLF("ABCD\n  1"   , 4) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD 1234 \n", 0) = 10
     *   StrUtil.lastIndexOfPrevCRLF("ABCD 1234 \r", 0) = 10
     *   StrUtil.lastIndexOfPrevCRLF("ABCD 1234 \n", 1) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD 1234 \r", 1) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD"        , 2) = -1
     *   StrUtil.lastIndexOfPrevCRLF("ABCD"        , 6) = -1
     *   StrUtil.lastIndexOfPrevCRLF(""            , 1) = -1
     *   StrUtil.lastIndexOfPrevCRLF(null          , 0) = -1
     *
     * </pre>
     * @param value
     * @param endOffset
     * @return
     */
    public static int lastIndexOfPrevCRLF(String value, int endOffset){
        if(isEmpty(value)){
            return -1;
        }
        int pos = value.length() - 1 - endOffset;
        if(pos < 0){
            return -1;
        }
        char c = 0;
        while(pos > 0){
            c = value.charAt(pos);
            if(c != LF && c != CR){
                pos--;
            }else{
                break;
            }
        }
        c = value.charAt(pos);
        if(pos == 0 && (c != LF && c != CR)){
            pos--;
        }
        return pos;
    }


    /**
     * <pre>
     * 対象文字列value中を検索して最初に発見した文字列keywordが、
     * 指定のエスケープ文字escapeCharでエスケープされていた場合にそのインデックスを返す。
     * 検索文字が無い場合は -1 を返す。
     * 検索対象文字列中に指定の検索文字列があっても、エスケープされていない場合は -2 を返す。
     * 検索対象文字列中に指定の検索文字列が複数あり、１つめはエスケープされておらず、
     * ２つめがエスケープされているような場合は、２つめのエスケープされている検索文字列の
     * インデックスを返す。
     *
     * Example:
     *   StrUtil.indexOfEscaped("0123%ABCD", "ABCD" , '%', 0  ) =  5 // found escaped keyword
     *   StrUtil.indexOfEscaped("0123-ABCD", "ABCD" , '%', 0  ) = -2 // found but not escaped
     *   StrUtil.indexOfEscaped("01234ABCD", "AACD" , '%', 0  ) = -1 // not found
     *   StrUtil.indexOfEscaped("  %ESC #ESC", "ESC", '%', 0  ) =  3 // found escaped keyword
     *   StrUtil.indexOfEscaped("  %ESC #ESC", "ESC", '#', 0  ) =  8 // found escaped keyword
     *   StrUtil.indexOfEscaped("  %ESC #ESC", "ESC", '%', 4  ) = -2 // found but not escaped
     *   StrUtil.indexOfEscaped("  %ESC #ESC", "ESC", '#', 4  ) =  8 // found escaped keyword
     *   StrUtil.indexOfEscaped("  %ESC #ESC", "ESC", '#', 999) = -1 // not found
     *
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @param offset
     * @return
     */
    public static int indexOfEscaped(String value, String keyword, char escapeChar, int offset){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }

        int foundIndex = -1;
        int valueLength = value.length();
        int searchIndex = offset;
//        boolean foundEscaped = false;
//        boolean foundKeyword = false;

        while(searchIndex < valueLength){
            foundIndex = value.indexOf(keyword, searchIndex);
            // found keyword.
            if(foundIndex > -1){
//                foundKeyword = true;
                if(foundIndex > 0 && value.charAt(foundIndex - 1) == escapeChar){
                    // escaped.
//                    foundEscaped = true;
                    break;
                }else{
                    // not escaped.
                    searchIndex = foundIndex + keyword.length();
                    if(searchIndex >= valueLength){
                        return -2;
                    }
                    continue;
                }
            }else{
                // not found. (foundIndex = -1)
                break;
            }
        }
//        if(foundKeyword && !foundEscaped){
//            // found keyword, but not escaped
//            foundIndex = -2;
//        }
        return foundIndex;

    }


    /**
     * <pre>
     * 対象文字列value中を指定インデックスから前方に検索して
     * 最初に発見した文字列keywordが、指定のエスケープ文字escapeCharで
     * エスケープされていた場合にそのインデックスを返す。
     * 検索文字が無い場合は -1 を返す。
     * 検索対象文字列中に指定の検索文字列があっても、エスケープされていない場合は -2 を返す。
     * 検索対象文字列中に指定の検索文字列が複数あり、末尾から数えて１つめはエスケープされておらず、
     * ２つめがエスケープされているような場合は、末尾から２つめに発見したエスケープされた
     * 検索文字列のインデックスを返す。
     *
     * Example:
     *   StrUtil.lastIndexOfEscaped("0123%ABCD", "ABCD" , '%'     ) =  5 // found escaped keyword
     *   StrUtil.lastIndexOfEscaped("0123-ABCD", "ABCD" , '%'     ) = -2 // found but not escaped
     *   StrUtil.lastIndexOfEscaped("01234ABCD", "AACD" , '%'     ) = -1 // not found
     *   StrUtil.lastIndexOfEscaped("  %ESC #ESC", "ESC", '%'     ) =  3 // found escaped keyword
     *   StrUtil.lastIndexOfEscaped("  %ESC #ESC", "ESC", '#'     ) =  8 // found escaped keyword
     *   StrUtil.lastIndexOfEscaped("  %ESC #ESC", "ESC", '%', 4  ) =  3 // found escaped keyword
     *   StrUtil.lastIndexOfEscaped("  %ESC #ESC", "ESC", '#', 7  ) = -2 // found but, not escaped
     *   StrUtil.lastIndexOfEscaped("  %ESC #ESC", "ESC", '#', 999) =  8 // found escaped keyword
     *
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @param endOffset 文字列中の検索開始オフセット位置
     * @return
     */
    public static int lastIndexOfEscaped(String value, String keyword, char escapeChar, int endOffset){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }
        int foundIndex = -1;
        int maxIndexOfValue = value.length() - 1;
        int searchIndex = maxIndexOfValue;
        if(endOffset < maxIndexOfValue){
            searchIndex = endOffset;
        }
        boolean foundEscaped = false;
        boolean foundKeyword = false;

        while(searchIndex > 0){
            foundIndex = value.lastIndexOf(keyword, searchIndex);
            // found keyword.
            if(foundIndex > -1){
                foundKeyword = true;
                if(foundIndex > 0 && value.charAt(foundIndex - 1) == escapeChar){
                    // escaped.
                    foundEscaped = true;
                    break;
                }else{
                    // not escaped.
                    searchIndex = foundIndex - 2;
                    if(searchIndex < 0){
                        return -2;
                    }
                    continue;
                }
            }else{
                // not found. (foundIndex = -1)
                break;
            }
        }
        if(foundKeyword && !foundEscaped){
            // found keyword, but not escaped
            foundIndex = -2;
        }
        return foundIndex;
    }


    /**
     * <pre>
     * return indexOfEscaped(value, keyword, escapeChar, 0);
     *
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @return
     */
    public static int indexOfEscaped(String value, String keyword, char escapeChar){
        return indexOfEscaped(value, keyword, escapeChar, 0);
    }


    /**
     * <pre>
     * return lastIndexOfEscaped(value, keyword, escapeChar, value.length() - 1);
     *
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @return
     */
    public static int lastIndexOfEscaped(String value, String keyword, char escapeChar){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }
        return lastIndexOfEscaped(value, keyword, escapeChar, value.length() - 1);
    }


    /**
     * <pre>
     * 検索した文字列が、指定のエスケープ文字でエスケープされていない場合に
     * そのインデックスを返す。
     * 検索文字列が対象文字列中に複数存在し、先に位置する文字列がエスケープ
     * されていた場合は、それをスキップして後方のエスケープされていない
     * 発見文字列のインデックスを返す。
     * 検索文字が無い場合は -1 を返す。
     * 検索対象文字列中に指定の検索文字列があっても、
     * エスケープされていた場合は -2 を返す。
     *
     * Example:
     *   StrUtil.indexOfNotEscaped("0123-ABCD", "ABCD",  '%', 0)   =  5
     *   StrUtil.indexOfNotEscaped("0123%ABCD", "ABCD",  '%', 0)   = -2
     *   StrUtil.indexOfNotEscaped("01234ABCD", "AACD",  '%', 0)   = -1
     *   StrUtil.indexOfNotEscaped("  %ESC #ESC", "ESC", '%', 0)   =  8
     *   StrUtil.indexOfNotEscaped("  %ESC #ESC", "ESC", '#', 0)   =  3
     *   StrUtil.indexOfNotEscaped("  %ESC #ESC", "ESC", '%', 4)   =  8
     *   StrUtil.indexOfNotEscaped("  %ESC #ESC", "ESC", '#', 4)   = -2
     *   StrUtil.indexOfNotEscaped("  %ESC #ESC", "ESC", '#', 999) = -1
     *   StrUtil.indexOfNotEscaped("%ESCAPED",    "",    '%', 0))  = -1
     *   StrUtil.indexOfNotEscaped("NOT ESCAPED", "NOT", '%', 0)   =  0
     *   StrUtil.indexOfNotEscaped("NOT ESCAPED", "NOT", '%', 1)   = -1
     *
     * </pre>
     * @param value 検索対象文字列
     * @param keyword 検索文字列
     * @param escapeChar エスケープ文字
     * @param offset 検索オフセットインデックス
     * @return
     */
    public static int indexOfNotEscaped(String value, String keyword, char escapeChar, int offset){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }
        int foundIndex = -1;
        int valueLength = value.length();
        int searchIndex = offset;
        boolean foundEscaped = false;

        while(searchIndex < valueLength){
            foundIndex = value.indexOf(keyword, searchIndex);
            // found keyword.
            if(foundIndex > -1){
                // but, escaped.
                if(foundIndex > 0 && value.charAt(foundIndex - 1) == escapeChar){
                    foundEscaped = true;
                    searchIndex = foundIndex + keyword.length();
                    if(searchIndex >= valueLength){
                        foundIndex = -2;
                        break;
                    }
                    continue;
                }else{
                    // not escaped.
                    break;
                }
            }else{
                // not found. (foundIndex = -1)
                break;
            }
        }
        if(foundIndex < 0 && foundEscaped){
            foundIndex = -2;
        }
        return foundIndex;


    }


    /**
     * <pre>
     * 末尾から検索した文字列が、指定のエスケープ文字でエスケープされていない場合に
     * そのインデックスを返す。
     * 検索文字列が対象文字列中に複数存在し、末尾から検索して先に発見した文字列が
     * エスケープされていた場合は、それをスキップして前方のエスケープされていない
     * 発見文字列のインデックスを返す。
     * 検索文字が無い場合は -1 を返す。
     * 検索対象文字列中に指定の検索文字列があっても、
     * エスケープされていた場合は -2 を返す。
     *
     * Example:
     *   StrUtil.lastIndexOfNotEscaped("0123-ABCD", "ABCD" , '%'   ) =  5
     *   StrUtil.lastIndexOfNotEscaped("0123%ABCD", "ABCD" , '%'   ) = -2
     *   StrUtil.lastIndexOfNotEscaped("01234ABCD", "AACD" , '%'   ) = -1
     *   StrUtil.lastIndexOfNotEscaped("  %ESC #ESC", "ESC", '%'   ) =  8
     *   StrUtil.lastIndexOfNotEscaped("  %ESC #ESC", "ESC", '%', 6) = -2
     *   StrUtil.lastIndexOfNotEscaped("  %ESC #ESC", "ESC", '#'   ) =  3
     *   StrUtil.lastIndexOfNotEscaped("  %ESC #ESC", "ESC", '#', 0) = -1
     *
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @param endOffset
     * @return
     */
    public static int lastIndexOfNotEscaped(String value, String keyword, char escapeChar, int endOffset){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }
        int foundIndex = -1;
        int maxIndexOfValue = value.length() - 1;
        int searchIndex = maxIndexOfValue;
        if(endOffset < maxIndexOfValue){
            searchIndex = endOffset;
        }
        boolean foundEscaped = false;

        while(searchIndex > 0){
            foundIndex = value.lastIndexOf(keyword, searchIndex);
            // found keyword.
            if(foundIndex > -1){
                // but, escaped.
                if(foundIndex > 0 && value.charAt(foundIndex - 1) == escapeChar){
                    foundEscaped = true;
                    searchIndex = foundIndex - 2;
                    if(searchIndex < 0){
                        foundIndex = -2;
                        break;
                    }
                    continue;
                }else{
                    // not escaped.
                    break;
                }
            }else{
                // not found. (foundIndex = -1)
                break;
            }
        }
        if(foundIndex < 0 && foundEscaped){
            foundIndex = -2;
        }
        return foundIndex;
    }


    /**
     * <pre>
     * return indexOfNotEscaped(String value, String keyword, char escapeChar, 0);
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @return
     */
    public static int indexOfNotEscaped(String value, String keyword, char escapeChar){
        return indexOfNotEscaped(value, keyword, escapeChar, 0);
    }


    /**
     * <pre>
     * return lastIndexOfNotEscaped(String value, String keyword, char escapeChar, 0);
     * </pre>
     * @param value
     * @param keyword
     * @param escapeChar
     * @return
     */
    public static int lastIndexOfNotEscaped(String value, String keyword, char escapeChar){
        if(isEmpty(value) || isEmpty(keyword)){
            return -1;
        }
        return lastIndexOfNotEscaped(value, keyword, escapeChar, value.length() - 1);
    }


    /**
     * <pre>
     * 引数１で指定される検索文字配列群(String[])の中に該当する文字列が、
     * 引数２の対象文字列の最初から探してヒットした場合に、
     * 一番最初にヒットした検索文字配列中の該当文字列の
     * 対象文字列中におけるインデックスを返す。
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     * １つもヒットが無ければ、-1を返す。
     *
     * Example:
     *   String[] list = {"WHERE","GROUP BY","HAVING","ORDER BY","LIMIT"};
     *
     *   //                       0         10        20        30        40
     *   //                index: 01234567890123456789012345678901234567890
     *   StrUtil.indexAny(list,  "select * from table where id='1'"          , true)  = 20
     *   StrUtil.indexAny(list,  " select * from table  limit 100   group by", true)  = 22
     *   StrUtil.indexAny(list,  " select * from table    group by"          , true)  = 24
     *   StrUtil.indexAny(list,  "this value is not containing any target."  , true)  = -1
     *   StrUtil.indexAny(list,  " select * from table where id='1'"         , false) = -1
     *   StrUtil.indexAny(list,  " select * from table  limit 100   group by", false) = -1
     *   StrUtil.indexAny(list,  " select * from table    group by"          , false) = -1
     *
     *
     *   String[] list1 = { "Orange", "Apple", "Banana" };
     *   String[] list2 = { "orange", "apple", "banana" };
     *   String[] nulls = null;
     *
     *   //                       0         10        20        30        40
     *   //                index: 01234567890123456789012345678901234567890
     *   StrUtil.indexAny(list1, "Peach, Avocado, Pear, Apple, Mango, Banana, Kiwi, Orange.", false) = 22
     *   StrUtil.indexAny(list1, "Peach, Apple, Avocado, Banana, Pear, Mango, Kiwi, Orange.", false) =  7
     *   StrUtil.indexAny(list1, "Peach, Avocado, Banana, Pear, Apple, Mango, Kiwi, Orange.", false) = 16
     *
     *   //                       0         10        20        30        40
     *   //                index: 01234567890123456789012345678901234567890
     *   StrUtil.indexAny(list2, "Peach, Avocado, Pear, Apple, Mango, Banana, Kiwi, Orange.", true ) = 22
     *   StrUtil.indexAny(list2, "Peach, Apple, Avocado, Banana, Pear, Mango, Kiwi, Orange.", true ) =  7
     *   StrUtil.indexAny(list2, "Peach, Avocado, Banana, Pear, Apple, Mango, Kiwi, Orange.", true ) = 16
     *
     *   StrUtil.indexAny(list2, "Peach, Avocado, Pear, Apple, Mango, Banana, Kiwi, Orange.", false) = -1
     *   StrUtil.indexAny(list2, "Peach, Apple, Avocado, Banana, Pear, Mango, Kiwi, Orange.", false) = -1
     *   StrUtil.indexAny(list2, "Peach, Avocado, Banana, Pear, Apple, Mango, Kiwi, Orange.", false) = -1
     *
     *   StrUtil.indexAny(null,  "Peach, Avocado, Pear, Apple, Mango, Banana, Kiwi, Orange.", false)  = -1
     *   StrUtil.indexAny(list2, null, false) = -1
     *   StrUtil.indexAny(list2, "", false)   = -1
     *   StrUtil.indexAny(null,  null, false) = -1
     *   StrUtil.indexAny(null,  "", false)   = -1
     *
     *
     * </pre>
     *
     * @param keywords
     * @param value
     * @param ignoreCase
     * @return ヒットした対象文字列中におけるインデックス
     */
    public static int indexAny( String[] keywords, String value, boolean ignoreCase ){
        if(isEmpty(value) || isEmpty(keywords)){
            return -1;
        }
        int firstIndex = value.length();
        if(ignoreCase){
            value = value.toLowerCase();
        }
        int idx = -1;
        for(int i=0; i<keywords.length; i++){
            if( ignoreCase ){
                idx = value.indexOf(keywords[i].toLowerCase());
            }else{
                idx = value.indexOf(keywords[i]);
            }
            if(idx > -1 && idx < firstIndex){
                firstIndex = idx;
            }
        }
        if(firstIndex == value.length()){
            firstIndex = -1;
        }
        return firstIndex;
    }


    /**
         * <pre>
         * 引数１で指定される検索文字配列群の中に該当する文字列が、引数２の対象文字列の末尾から
         * 探してヒットした場合に、一番最初にヒットした検索文字配列中の該当文字列の
         * 対象文字列中におけるインデックスを返す。
         * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
         * １つもヒットが無ければ、-1を返す。
         *
         * Example:
         *   String[] list = {"WHERE","GROUP BY","HAVING","ORDER BY","LIMIT"};
         *
         *   //                          0        10        20        30        40
         *   //                    index:01234567890123456789012345678901234567890
         *   StrUtil.lastIndexAny(list, "select * from table where id='1'", true)           = 20
         *   StrUtil.lastIndexAny(list, " select * from table  limit 100   group by", true) = 34
         *   StrUtil.lastIndexAny(list, "where group by having order by limit", true)       = 31
         *   StrUtil.lastIndexAny(list, "Where are you from ?", true)                       =  0
         *   StrUtil.lastIndexAny(list, "this value is not containing any target.", true)   = -1
         *   StrUtil.lastIndexAny(list, "select * from table where id='1'", false)          = -1
         *   StrUtil.lastIndexAny(list, "where group by having order by limit", false)      = -1
         *   StrUtil.lastIndexAny(list, "this value is not containing any target.", true)   = -1
         *
         * </pre>
         *
         * @param keywords
         * @param value
         * @param ignoreCase
         * @return 一番最初にヒットした該当文字列の対象文字列中におけるインデックス
         */
        public static int lastIndexAny(String[] keywords, String value, boolean ignoreCase){
            if(isEmpty(value) || isEmpty(keywords)){
                return -1;
            }
            int lastIndex = -1;
            if(ignoreCase){
                value = value.toLowerCase();
            }
            int idx = -1;
            for(int i=0; i<keywords.length; i++){
                if( ignoreCase ){
                    idx = value.lastIndexOf(keywords[i].toLowerCase());
                }else{
                    idx = value.lastIndexOf(keywords[i]);
                }
                if(idx > -1 && idx > lastIndex){
                    lastIndex = idx;
                }
            }
            return lastIndex;
        }


    /**
     * <pre>
     * 引数１で指定される検索文字配列群(char[])の中に該当する文字列が、引数２の対象文字列の最初から
     * 探してヒットした場合に、一番最初にヒットした検索文字配列中の該当文字列の
     * 対象文字列中におけるインデックスを返す。
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     * １つもヒットが無ければ、-1を返す。
     *
     * Example:
     *    char[] list = { 'A', 'a', 'Z', 'z', '1', '2' };
     *    StrUtil.indexAny(list, "2zCA", false) =  0
     *    StrUtil.indexAny(list, "3zCA", false) =  1
     *    StrUtil.indexAny(list, "CBa" , true ) =  2
     *    StrUtil.indexAny(list, "BbB" , true ) = -1
     *    StrUtil.indexAny(list, ""    , true ) = -1
     *    StrUtil.indexAny(list, ""    , false) = -1
     *    StrUtil.indexAny(list, null  , true ) = -1
     *    StrUtil.indexAny(list, null  , false) = -1
     *    StrUtil.indexAny((char[])null, "AaZz12", false) = -1
     *
     *    char[] list1 = {' ', '\t', '\r', '\n'};
     *    StrUtil.indexAny(list1, "<A href =\t", true) = 2
     *    StrUtil.indexAny(list1, "<A_href =\t", true) = 7
     *    StrUtil.indexAny(list1, "<A_href_=\t", true) = 9
     *
     * @param keychars
     * @param value
     * @param ignoreCase
     * @return
     */
    public static int indexAny( char[] keychars, String value, boolean ignoreCase ){
        if(isEmpty(value)
                || keychars == null
                || keychars.length == 0){
            return -1;
        }
        int firstIndex = value.length();
        if(ignoreCase){
            value = value.toLowerCase();
        }
        int idx = -1;
        for(int i=0; i<keychars.length; i++){
            if( ignoreCase ){
                idx = value.indexOf(Character.toString(keychars[i]).toLowerCase());
            }else{
                idx = value.indexOf(keychars[i]);
            }
            if(idx > -1 && idx < firstIndex){
                firstIndex = idx;
            }
        }
        if(firstIndex == value.length()){
            firstIndex = -1;
        }
        return firstIndex;
    }


    /**
     * <pre>
     * 引数１で与えられた１語と同じ語が、引数２で与えられた文字配列中に
     * あった場合、その文字配列中の該当文字が格納されている部分の
     * 配列インデックスを返す。
     *
     * Example:
     *   String[] list = {"Apple", "Orange", "Banana", "Peach"};
     *   String[] nulls = null;
     *
     *   StrUtil.arrayIndexOf("apple", list, true)    = 0
     *   StrUtil.arrayIndexOf("banana", list, true)   = 2
     *   StrUtil.arrayIndexOf("peach", list, true)    = 3
     *   StrUtil.arrayIndexOf("apple", list, false)   = -1
     *   StrUtil.arrayIndexOf("banana", list, false)  = -1
     *   StrUtil.arrayIndexOf("peach", list, false)   = -1
     *   StrUtil.arrayIndexOf("Orange", nulls, false) = -1
     *   StrUtil.arrayIndexOf(null, list, false)      = -1
     *
     * </pre>
     *
     * @param keyword 調べたい１語
     * @param values 調査対象となる文字列配列
     * @param ignoreCase 大文字小文字を問わないならtrue
     * @return 合致した文字の配列インデックス。合致が無ければ-1を返す。
     *
     */
    public static int arrayIndexOf(String keyword, String[] values, boolean ignoreCase) {
        return arrayIndexOf(keyword, values, ignoreCase, 0);
    }


    /**
     * <pre>
     * 引数１で与えられた１語と同じ語が、引数２で与えられた文字配列中に
     * あった場合、その文字配列中の該当文字が格納されている部分の
     * 配列インデックスを返す。
     * 配列内の検索開始インデックスを指定できる。
     *
     * Example:
     *   String[] list = {"Apple", "Orange", "Banana", "Peach"};
     *   String[] nulls = null;
     *
     *   StrUtil.arrayIndexOf("apple" , list,  true,   0) =  0  // list[0]-list[3]
     *   StrUtil.arrayIndexOf("peach" , list,  true,   2) =  3  // list[2]-list[3]
     *   StrUtil.arrayIndexOf("peach" , list,  true,   3) =  3  // list[3]-list[3]
     *   StrUtil.arrayIndexOf("orange", list,  true,   1) =  1  // list[1]-list[3]
     *   StrUtil.arrayIndexOf("orange", list,  true,   2) = -1  // list[2]-list[3]
     *   StrUtil.arrayIndexOf("Apple" , list,  false,  1) = -1  // list[1]-list[3]
     *   StrUtil.arrayIndexOf("apple" , nulls, true,   0) = -1  // list[0]-list[3]
     *   StrUtil.arrayIndexOf(null    , list,  true,   0) = -1  // list[0]-list[3]
     *   StrUtil.arrayIndexOf(null    , list,  true, 100) = -1  // list[100]-
     *   StrUtil.arrayIndexOf("apple" , list,  true, 100) = ArrayIndexOutOfBoundsException
     *
     * </pre>
     * @param keyword 調べたい１語
     * @param values 調査対象となる文字列配列
     * @param ignoreCase 大文字小文字を問わないならtrue
     * @param offset 配列の検索開始インデックス(list[startIndex])
     * @return 合致した文字の配列インデックス。合致が無ければ-1を返す。
     */
    public static int arrayIndexOf(String keyword, String[] values, boolean ignoreCase, int offset) {
        if(isEmpty(values) || isEmpty(keyword)){
            return -1;
        }
        if( offset >= values.length ){
            throw new ArrayIndexOutOfBoundsException(
                      "\n\t searchWord = " + keyword
                    + "\n\t " + toStringFromArray(values, true)
                    + "\n\t startIndex = " + offset + ": but array.length = " + values.length + "."
                    );
        }

        int rtn = -1;
        boolean matched = false;
        if(ignoreCase){
            keyword = keyword.toLowerCase();
        }
        for(int i=offset; i<values.length; i++){
            if(ignoreCase){
                matched = keyword.equals(values[i].toLowerCase());
            }else{
                matched = keyword.equals(values[i]);
            }
            if( matched ){
                rtn = i;
                break;
            }
        }
        return rtn;
    }


    /**
     * <pre>
     * java.lang.String#startsWith()の大文字小文字を問わない版。
     *
     * Example:
     *   StrUtil.startIgnoreCaseWith("apple", "Apple Computer, Inc.") = true
     *   StrUtil.startIgnoreCaseWith("ApPle", "Apple Computer, Inc.") = true
     *   StrUtil.startIgnoreCaseWith("@pple", "Apple Computer, Inc.") = false
     * </pre>
     *
     * @param keyword
     * @param value
     * @return 引数２の先頭が引数１で始まっていればtrue
     */
    public static boolean startIgnoreCaseWith(String keyword, String value){
        return (value.toLowerCase()).startsWith(keyword.toLowerCase());
    }


    /**
     * <pre>
     * 与えられた検索対象文字列の先頭の半角スペースとタブを無視して、
     * その次から始まる文字が。検索文字列と合致しているかどうか。
     *
     * Example:
     *   StrUtil.startIgnoreSpacesAndTabsWith("//", "        //----") = true
     *   StrUtil.startIgnoreSpacesAndTabsWith("//", "\t \t   //----") = true
     *   StrUtil.startIgnoreSpacesAndTabsWith("//", "\t \t   /*----") = false
     *   StrUtil.startIgnoreSpacesAndTabsWith("//", "\t \n   /*----") = false
     *   StrUtil.startIgnoreSpacesAndTabsWith("//", "   \r   /*----") = false
     *
     * </pre>
     * @param keyword
     * @param value
     * @return
     */
    public static boolean startIgnoreSpacesAndTabsWith(String keyword, String value){
        if(isEmpty(keyword) ){
            return false;
        }
        boolean result = false;
        int index = indexOfNextChar(value, 0);
        if( index > -1){
            result = value.startsWith(keyword, index);
        }
        return result;
    }


    /**
     * <pre>
     * 与えられた文字列のstartIndexで示される位置から続く部分が、
     * 文字列配列中にある文字列で始まっているかどうか。
     *
     * Example:
     *   String[] list = { "Apple", "Orange", "Banana", "Computer" };
     *   String[] nulls = null;
     *
     *   StrUtil.startWithAny(list, "Apple Computer Inc.,", 0, true ) = true
     *   StrUtil.startWithAny(list, "Apple Computer Inc.,", 6, true ) = true
     *   StrUtil.startWithAny(list, "Apple Computer Inc.,", 1, true ) = false
     *   StrUtil.startWithAny(list, "Apple Computer Inc.,", 5, true ) = false
     *   StrUtil.startWithAny(list, "apple computer inc.,", 0, false) = false
     *   StrUtil.startWithAny(list, "apple computer inc.,", 6, false) = false
     *   StrUtil.startWithAny(null, "apple computer inc.,", 0, false) = false
     *   StrUtil.startWithAny(list, null,                   0, false) = false
     *   StrUtil.startWithAny(null, null,                   0, false) = false
     *   StrUtil.startWithAny(list, "",                     0, false) = false
     *
     * </pre>
     * @param keywords
     * @param value
     * @param offset
     * @param ignoreCase
     * @return
     */
    public static boolean startWithAny(String[] keywords, String value, int offset, boolean ignoreCase) {
        if(isEmpty(value) || isEmpty(keywords)){
            return false;
        }
        if(offset > value.length()){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t startIndex = " + offset + ": but value.length() = " + value.length()+ "."
                    );
        }
        String chkWord = value;
        if(ignoreCase){
            chkWord = chkWord.toLowerCase();
        }
        for(int i=0; i<keywords.length; i++){
            String chkInList = keywords[i];
            if(ignoreCase){
                chkInList = chkInList.toLowerCase();
            }
            if( chkWord.startsWith( chkInList, offset )){
                return true;
            }
        }
        return false;
    }


    /**
     * <pre>
     * 与えられた文字列が、文字列配列中にある文字列で始まっているかどうか。
     *
     * Example:
     *   String[] list = { "Orange", "Banana", "Apple" };
     *   String[] nulls = null;
     *
     *   StrUtil.startWithAny(list, "Apple Computer Inc.,",  true ) = true
     *   StrUtil.startWithAny(list, "apple computer inc.,",  true ) = true
     *   StrUtil.startWithAny(list, "apple computer inc.,",  false) = false
     *   StrUtil.startWithAny(null, "apple computer inc.,",  false) = false
     *   StrUtil.startWithAny(list, null,                    false) = false
     *   StrUtil.startWithAny(null, null,                    false) = false
     *   StrUtil.startWithAny(list, "",                      false) = false
     *
     * </pre>
     * @param keywords
     * @param value
     * @param ignoreCase
     * @return
     */
    public static boolean startWithAny(String[] keywords, String value, boolean ignoreCase) {
        return startWithAny(keywords, value, 0, ignoreCase);
    }


    /**
     * <pre>
     * 文字列配列中の指定の「開始配列インデックス」以降に、検索したい文字列があるかどうかを調べ、
     * あったら、開始配列インデックスから発見した文字列までの間を取得して返す。
     * 取得する配列の最後尾は、オフセット調整可能。
     *
     * Example:
     *   String[] list = { "0", "1", "{", "3", "4", "}", "6", "7" };
     *   String[] nulls = null;
     *
     *   StrUtil.regionArray(list, "}", true, 0,  0) = {"0", "1", "{", "3", "4", "}"}
     *   StrUtil.regionArray(list, "}", true, 1,  0) = {"1", "{", "3", "4", "}"     }
     *   StrUtil.regionArray(list, "}", true, 2,  0) = {"{", "3", "4", "}"          }
     *   StrUtil.regionArray(list, "}", true, 2, -1) = {"{", "3", "4"               }
     *   StrUtil.regionArray(list, "}", true, 2, -2) = {"{", "3"                    }
     *   StrUtil.regionArray(list, "}", true, 2, -3) = {"{"                         }
     *   StrUtil.regionArray(list, "}", true, 2, -4) = ArrayIndexOutOfBoundsException
     *
     *   StrUtil.regionArray(list, "}", true, 2,  1) = {"{", "3", "4", "}", "6"}
     *   StrUtil.regionArray(list, "}", true, 3,  1) = {"3", "4", "}", "6"     }
     *   StrUtil.regionArray(list, "}", true, 4,  1) = {"4", "}", "6"          }
     *   StrUtil.regionArray(list, "}", true, 5,  1) = {"}", "6"               }
     *   StrUtil.regionArray(list, "}", true, 5,  2) = {"}", "6", "7"          }
     *   StrUtil.regionArray(list, "}", true, 5,  3) = ArrayIndexOutOfBoundsException
     *   StrUtil.regionArray(list, "}", true, 8,  0) = ArrayIndexOutOfBoundsException
     *
     *   StrUtil.regionArray(list,  "}" , true, 6,  1) = null
     *   StrUtil.regionArray(list,  null, true, 0,  0) = null
     *   StrUtil.regionArray(nulls, "}" , true, 0,  0) = null
     *
     * </pre>
     * @param values
     * @param keyword
     * @param ignoreCase
     * @param startIndex
     * @param endOffset
     * @return 結果の配列。list中にsearchWordが無ければnullを返す。
     */
    public static String[] regionArray(String[] values, String keyword, boolean ignoreCase, int startIndex, int endOffset) {
        if(isEmpty(values) || isEmpty(keyword) ){
            return null;
        }
        if( startIndex >= values.length ){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t startIndex = " + startIndex + ": but array.length = " + values.length + "."
                    );
        }
        if(ignoreCase){
            keyword = keyword.toLowerCase();
        }
        int newArraySize = 0;
        boolean found = false;
        int foundIndex = startIndex;
        while( foundIndex < values.length){
            String item = values[foundIndex];
            if(ignoreCase){
                item = item.toLowerCase();
            }
            newArraySize++;
            if( keyword.equals(item) ){
                found = true;
                break;
            }
            foundIndex++;
        }
        if(!found){
            return null;
        }

        newArraySize += endOffset;

        if( (newArraySize < 1) || (newArraySize + startIndex > values.length)){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t Maybe 'endOffset' is not correct." +
                    "\n\t --------" +
                    "\n\t Target array =\n\t\t"+toStringFromArray(values, false) +
                    "\n\t Target array size          = "+ values.length +
                    "\n\t Search word                = \"" + keyword + "\"" +
                    "\n\t Start index for search     = " + startIndex +
                    "\n\t endOffset                  = " + endOffset +
                    "\n\t Found index of search word = " + foundIndex +
                    "\n\t New array size             = " + newArraySize +
                    "\n\t *** New array size is added value of endOffset." +
                    "\n"
                    );
        }
        String[] newArray = new String[newArraySize];
        int endIndex = startIndex + newArraySize - 1;
        copyArray(values, newArray, startIndex, endIndex);
        return newArray;

    }


    /**
     * <pre>
     * WildCardを用いた文字列比較で、比較対象文字列のマッチング範囲の
     * マッチング範囲の最初と最後尾のインデックスをint型配列で返す。
     *
     * Example:
     *   StrUtil.regionIndexWithWildCard( "_", "A",false)        = {0,0}    // ---> "A"
     *   StrUtil.regionIndexWithWildCard( "_", "AA",false)       = {0,0}    // ---> "A"
     *   StrUtil.regionIndexWithWildCard( "__", "A", false)      = null
     *   StrUtil.regionIndexWithWildCard( "01_", "01A",false)    = {0,2}    // ---> "01A"
     *   StrUtil.regionIndexWithWildCard( "01A_", "01A", false)  = null
     *
     *   StrUtil.regionIndexWithWildCard( "________", "0123ABCD",false)          = {0,7}    // ---> "0123ABCD"
     *   StrUtil.regionIndexWithWildCard( "A Wil_ Car_.", "A Wild Card.",false)  = {0,11}    // ---> "A Wild Card."
     *   StrUtil.regionIndexWithWildCard( "a wil_ car_.", "A Wild Card.",true)   = {0,11}    // ---> "A Wild Card."
     *   StrUtil.regionIndexWithWildCard( "a wil_ car_.", "A Wild Card.", false) = null
     *   StrUtil.regionIndexWithWildCard( "a wild card_", "A Wild Card.",true)   = {0,11}    // ---> "A Wild Card."
     *   StrUtil.regionIndexWithWildCard( "a wild card_", "A Wild Card!!",true)  = {0,11}    // ---> "A Wild Card!"
     *   StrUtil.regionIndexWithWildCard( "a wild card__", "A Wild Card!!",true) = {0,12}    // ---> "A Wild Card!!"
     *   StrUtil.regionIndexWithWildCard( "a wild card___", "A Wild Card!!", true) = null
     *
     *   StrUtil.regionIndexWithWildCard( "A Space$Or$Tab", "A Space Or\tTab",false)     = {0,13}    // ---> "A Space Or\tTab"
     *   StrUtil.regionIndexWithWildCard( "A Space$$$Or$Tab", "A Space   Or\tTab",false) = {0,15}    // ---> "A Space   Or\tTab"
     *   StrUtil.regionIndexWithWildCard( "A Space$Or$Tab", "A Space   Or\tTab", false)  = null
     *   StrUtil.regionIndexWithWildCard( "A Space$Or$Tab", "A Space Or\t\tTab", false)  = null
     *   StrUtil.regionIndexWithWildCard( "A Space$Or$$Tab", "A Space Or\t\tTab",false)  = {0,14}    // ---> "A Space Or\t\tTab"
     *   StrUtil.regionIndexWithWildCard( "A Space$Or$Tab", "A Space\nOr\rTab", false)   = null
     *   StrUtil.regionIndexWithWildCard( "A Space Or Tab$", "A Space Or Tab ",false)    = {0,14}    // ---> "A Space Or Tab "
     *   StrUtil.regionIndexWithWildCard( "A Space Or Tab$", "A Space Or Tab \t",false)  = {0,14}    // ---> "A Space Or Tab "
     *   StrUtil.regionIndexWithWildCard( "A Space Or Tab$$", "A Space Or Tab ", false)  = null
     *   StrUtil.regionIndexWithWildCard( "a sPACE oR TAB$", "A Space Or Tab\t\t",true)  = {0,14}    // ---> "A Space Or Tab\t"
     *
     *   StrUtil.regionIndexWithWildCard( "Spaces$+Or$+Tabs", "Spaces Or Tabs",false)    = {0,13}    // ---> "Spaces Or Tabs"
     *   StrUtil.regionIndexWithWildCard( "Spaces$+Or$+Tabs", "Spaces  Or  Tabs",false)  = {0,15}    // ---> "Spaces  Or  Tabs"
     *   StrUtil.regionIndexWithWildCard( "Spaces$+Or$+Tabs", "Spaces  \t  Or  \t  Tabs",false)   = {0,21}    // ---> "Spaces  \t  Or  \t  Tabs"
     *   StrUtil.regionIndexWithWildCard( "Spaces$+$+Or$+Tabs", "Spaces  Or  Tabs", false)        = null
     *   StrUtil.regionIndexWithWildCard( "Spaces Or Tabs$+", "Spaces Or Tabs \t \t \t",false)    = {0,19}    // ---> "Spaces Or Tabs \t \t \t"
     *   StrUtil.regionIndexWithWildCard( "Spaces Or Tabs$+$+", "Spaces Or Tabs \t \t \t", false) = null
     *   StrUtil.regionIndexWithWildCard( "$+Spaces$+Or$+Tabs", "   Spaces Or Tabs",false)        = {0,16}    // ---> "   Spaces Or Tabs"
     *   StrUtil.regionIndexWithWildCard( "\t$$$Spaces$+Or$+Tabs", "\t   Spaces Or Tabs",false)   = {0,17}    // ---> "\t   Spaces Or Tabs"
     *   StrUtil.regionIndexWithWildCard( "\t$$$spaces$+or$+tabs", "\t   Spaces Or Tabs", false)  = null
     *   StrUtil.regionIndexWithWildCard( "\t$$$spaces$+or$+tabs", "\t   Spaces Or Tabs",true)    = {0,17}    // ---> "\t   Spaces Or Tabs"
     *
     *   StrUtil.regionIndexWithWildCard( "CR or LF ~ / ~", "CR or LF \r / \n",false)    = {0,13}    // ---> "CR or LF \r / \n"
     *   StrUtil.regionIndexWithWildCard( "CR or LF ~~ / ~", "CR or LF \r\n / \n",false) = {0,14}    // ---> "CR or LF \r\n / \n"
     *   StrUtil.regionIndexWithWildCard( "CR or LF ~ / ~~", "CR or LF \r / \n\r",false) = {0,14}    // ---> "CR or LF \r / \n\r"
     *   StrUtil.regionIndexWithWildCard( "CRLF at End Of Line~~", "CRLF at End Of Line\n\r",false) = {0,20}    // ---> "CRLF at End Of Line\n\r"
     *   StrUtil.regionIndexWithWildCard( "CRLF at End Of Line~", "CRLF at End Of Line\n\r",false)  = {0,19}    // ---> "CRLF at End Of Line\n"
     *   StrUtil.regionIndexWithWildCard( "CRLF at End Of Line~~", "CRLF at End Of Line\n", false)  = null
     *
     *   StrUtil.regionIndexWithWildCard( "CRs and LFs~+", "CRs and LFs\r\n\r\n",false)      = {0,14}    // ---> "CRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "CRs and LFs~~+", "CRs and LFs\r\n\r\n",false)     = {0,14}    // ---> "CRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "CRs and LFs~~~+", "CRs and LFs\r\n\r\n",false)    = {0,14}    // ---> "CRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "CRs and LFs~~~~+", "CRs and LFs\r\n\r\n",false)   = {0,14}    // ---> "CRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "CRs and LFs~~~~~+", "CRs and LFs\r\n\r\n", false) = null
     *   StrUtil.regionIndexWithWildCard( "~+CRs and LFs~+", "\r\n\r\nCRs and LFs\r\n\r\n",false)      = {0,18}    // ---> "\r\n\r\nCRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "~~~+CRs and LFs~+", "\r\n\r\nCRs and LFs\r\n\r\n",false)    = {0,18}    // ---> "\r\n\r\nCRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "~~~~+CRs and LFs~+", "\r\n\r\nCRs and LFs\r\n\r\n",false)   = {0,18}    // ---> "\r\n\r\nCRs and LFs\r\n\r\n"
     *   StrUtil.regionIndexWithWildCard( "~~~~~+CRs and LFs~+", "\r\n\r\nCRs and LFs\r\n\r\n", false) = null
     *
     *   StrUtil.regionIndexWithWildCard( "Space,Tab,CR,LF [&&&&]", "Space,Tab,CR,LF [ \t\r\n]",false) = {0,21}    // ---> "Space,Tab,CR,LF [ \t\r\n]"
     *   StrUtil.regionIndexWithWildCard( "Space,Tab,CR,LF [&+]", "Space,Tab,CR,LF [ \t\r\n]",false)   = {0,21}    // ---> "Space,Tab,CR,LF [ \t\r\n]"
     *   StrUtil.regionIndexWithWildCard( "Space,Tab,CR,LF [&&+]", "Space,Tab,CR,LF [ \t\r\n]",false)  = {0,21}    // ---> "Space,Tab,CR,LF [ \t\r\n]"
     *   StrUtil.regionIndexWithWildCard( "Space,Tab,CR,LF [&+&]", "Space,Tab,CR,LF [ \t\r\n]", false) = null
     *   StrUtil.regionIndexWithWildCard( "Space,CR&+[&+]", "Space,CR        [ \r]",false)             = {0,19}    // ---> "Space,CR        [ \r]"
     *   StrUtil.regionIndexWithWildCard( "Space,CR&+[$&+]", "Space,CR        [ \r]",false)            = {0,19}    // ---> "Space,CR        [ \r]"
     *   StrUtil.regionIndexWithWildCard( "Space,CR&+[$~]", "Space,CR        [ \r]",false)             = {0,19}    // ---> "Space,CR        [ \r]"
     *   StrUtil.regionIndexWithWildCard( "Space,CR&+[~+]", "Space,CR        [ \r]", false)            = null
     *   StrUtil.regionIndexWithWildCard( "Space,CR&+[$+]", "Space,CR        [ \r]", false)            = null
     *
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:#", "NumberCheck:4",false)            = {0,12}    // ---> "NumberCheck:4"
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:#", "NumberCheck:45",false)           = {0,12}    // ---> "NumberCheck:4"
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:##", "NumberCheck:4", false)          = null
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:####", "NumberCheck:0123",false)      = {0,15}    // ---> "NumberCheck:0123"
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:####", "NumberCheck:0123456",false)   = {0,15}    // ---> "NumberCheck:0123"
     *   StrUtil.regionIndexWithWildCard( "NumberCheck:#####", "NumberCheck:0123", false)    = null
     *   StrUtil.regionIndexWithWildCard( "numbercheck:####", "NumberCheck:0123",true)       = {0,15}    // ---> "NumberCheck:0123"
     *   StrUtil.regionIndexWithWildCard( "numbercheck:####", "NumberCheck:0123", false)     = null
     *
     *   StrUtil.regionIndexWithWildCard( "Numbers:#+", "Numbers:123",false)                 = {0,10}    // ---> "Numbers:123"
     *   StrUtil.regionIndexWithWildCard( "Numbers:##+", "Numbers:123",false)                = {0,10}    // ---> "Numbers:123"
     *   StrUtil.regionIndexWithWildCard( "Numbers:#+#", "Numbers:123", false)               = null
     *
     *   StrUtil.regionIndexWithWildCard( "UserID=A#+-Z#+", "UserID=A45-Z9876",false)       = {0,15}    // ---> "UserID=A45-Z9876"
     *   StrUtil.regionIndexWithWildCard( "UserID=A##-Z#+", "UserID=A45-Z9876",false)       = {0,15}    // ---> "UserID=A45-Z9876"
     *   StrUtil.regionIndexWithWildCard( "UserID=A#+-Z####", "UserID=A45-Z9876",false)     = {0,15}    // ---> "UserID=A45-Z9876"
     *   StrUtil.regionIndexWithWildCard( "TEL:+81-##-#+-#+", "TEL:+81-03-1234-5678",false) = {0,19}    // ---> "TEL:+81-03-1234-5678"
     *
     *   StrUtil.regionIndexWithWildCard( "Alphabet=A@", "Alphabet=AZ",false) = {0,10}    // ---> "Alphabet=AZ"
     *   StrUtil.regionIndexWithWildCard( "Alphabet=@Z", "Alphabet=AZ",false) = {0,10}    // ---> "Alphabet=AZ"
     *   StrUtil.regionIndexWithWildCard( "Alphabet=@@", "Alphabet=AZ",false) = {0,10}    // ---> "Alphabet=AZ"
     *   StrUtil.regionIndexWithWildCard( "Alphabet=ABC@@@", "Alphabet=ABCXYZ",false)  = {0,14}    // ---> "Alphabet=ABCXYZ"
     *   StrUtil.regionIndexWithWildCard( "Alphabet=@1@2@3", "Alphabet=A1B2C3",false)  = {0,14}    // ---> "Alphabet=A1B2C3"
     *   StrUtil.regionIndexWithWildCard( "Alphabet=@1@2@3", "Alphabet=A1b2c3",false)  = {0,14}    // ---> "Alphabet=A1b2c3"
     *   StrUtil.regionIndexWithWildCard( "aLpHaBeT=@1@2@3", "Alphabet=a1B2c3",true)   = {0,14}    // ---> "Alphabet=a1B2c3"
     *   StrUtil.regionIndexWithWildCard( "aLpHaBeT=@1@2@3", "alphabet=a1b2C3", false) = null
     *
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@+1200", "Alphabets=TZR1200",false)         = {0,16}    // ---> "Alphabets=TZR1200"
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@+", "Alphabets=HF456-ZXY",false)     = {0,18}    // ---> "Alphabets=HF456-ZXY"
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@@+", "Alphabets=HF456-ZXY",false)    = {0,18}    // ---> "Alphabets=HF456-ZXY"
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@@@+", "Alphabets=HF456-ZXY",false)   = {0,18}    // ---> "Alphabets=HF456-ZXY"
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@@@@+", "Alphabets=HF456-ZXY", false) = null
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@+@", "Alphabets=HF456-ZXY", false)   = null
     *   StrUtil.regionIndexWithWildCard( "Alphabets=@@456-@+@+", "Alphabets=HF456-ZXY", false)  = null
     *
     *   StrUtil.regionIndexWithWildCard( "Any Chars %", "Any Chars ABC123",false)   = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any Chars%", "Any Chars ABC123",false)    = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any %", "Any Chars ABC123",false)         = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any%", "Any Chars ABC123",false)          = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any % ABC123", "Any Chars ABC123",false)  = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any%ABC123", "Any Chars ABC123",false)    = {0,15}    // ---> "Any Chars ABC123"
     *   StrUtil.regionIndexWithWildCard( "Any % ABC124", "Any Chars ABC123", false) = null
     *   StrUtil.regionIndexWithWildCard( "Any%ABC122", "Any Chars ABC123", false)   = null
     *   StrUtil.regionIndexWithWildCard( "Any % ABC%", "Any Chars ABC123",false)    = {0,15}    // ---> "Any Chars ABC123"
     *
     *   StrUtil.regionIndexWithWildCard( "amount=\\$1##", "Amount=$100",true)                       = {0,10}    // ---> "Amount=$100"
     *   StrUtil.regionIndexWithWildCard( "amount=\\\\100", "Amount=\\100",true)                     = {0,10}    // ---> "Amount=\\100"
     *   StrUtil.regionIndexWithWildCard( "Escape\\_Char\\~", "Escape_Char~",false)                  = {0,11}    // ---> "Escape_Char~"
     *   StrUtil.regionIndexWithWildCard( "@+,Orange\\&Pe@+", "Apple,Orange&Peach",false)            = {0,17}    // ---> "Apple,Orange&Peach"
     *   StrUtil.regionIndexWithWildCard( "Percentage$+:&+##\\%", "Percentage  :  87%",false)        = {0,17}    // ---> "Percentage  :  87%"
     *   StrUtil.regionIndexWithWildCard( "amount=\\\\100,$+\\\\#+", "Amount=\\100, \\200",true)     = {0,16}    // ---> "Amount=\\100, \\200"
     *   StrUtil.regionIndexWithWildCard( "user\\_id\\@yourmail.@+", "user_id@yourmail.org",false)   = {0,19}    // ---> "user_id@yourmail.org"
     *   StrUtil.regionIndexWithWildCard( "\\# \\$+\\$+\\$ [comment is @+] \\$+\\$+\\$", "# $+$+$ [comment is here] $+$+$",false) = {0,30}    // ---> "# $+$+$ [comment is here] $+$+$"
     *   StrUtil.regionIndexWithWildCard( "%.cgi\\?key#=value#\\&@+2=@+2", "http://www.example.com/pl.cgi?key1=value1&key2=value2",false) = {0,52}    // ---> "http://www.example.com/pl.cgi?key1=value1&key2=value2"
     *
     *   StrUtil.regionIndexWithWildCard( "<@ %{{*ftp://|http://|https://}}`+", "<a href=\"https://URL",true, 0) = {0, 19}
     *   StrUtil.regionIndexWithWildCard( "@LPHABET: %{{*choice-1|choice-2|choice-3}}`", "ALPHABET: ===choice-2/",true, 0) = {0, 21}
     *   StrUtil.regionIndexWithWildCard( "@LPHABET: %{{*choice-1|choice-2|choice-3}}`+", "ALPHABET: ***choice-2/dir1/dir2/",true, 0) = {0, 31}
     *
     *   StrUtil.regionIndexWithWildCard( "@LPHABET: %{{*choice-1|choice-2|choice-3}}", "ALPHABET: ===choice-3",true, 0) = {0, 20}
     *   StrUtil.regionIndexWithWildCard(   "%:001={{*choice-1|choice-2|choice-3}}",    "CODE:001=choice-1",true, 0)     = {0, 16}
     *   StrUtil.regionIndexWithWildCard( "%\\_001={{*choice-1|choice-2|choice-3}}",    "CODE_001=choice-2",true, 0)     = {0, 16}
     *   StrUtil.regionIndexWithWildCard(   "%:001={{*choice-1|choice-2|choice-3}}",    ":001=choice-3",true, 0)         = {0, 12}
     *
     *   StrUtil.regionIndexWithWildCard(   "%:001={{*choice-1|choice-2|choice-3}}",     "001=choice-3",true, 0) = null
     *   StrUtil.regionIndexWithWildCard(   "%:001={{*choice-1|choice-2|choice-3}}",    ":001=choice-4",true, 0) = null
     *
     *
     *
     *   // OR check
     *   StrUtil.regionIndexWithWildCard( "{{*http://|https://}}`+", "<a href=\"https://www.site.org/foo.cgi?bar=123>Link</a>",true, 0)        = {9,44} // ---> "https://www.site.org/foo.cgi?bar=123"
     *   // NOT check
     *   StrUtil.regionIndexWithWildCard( "<a href=\"{{!http://|https://}}`+", "<A HREF=\"ftp://fileserver/file2.zip\">file2.zip</A>",true, 0) = {0,34} // ---> "<A HREF="ftp://fileserver/file2.zip"
     *
     * --------------------------------------------------------------------
     * WildCardを用いた文字列比較で、比較対象文字列のマッチング範囲の
     * マッチング範囲の最初と最後尾のインデックスをint型配列で返す。
     *
     * Example-1:
     *   // Web URL Extracting.
     *   public void getURL_usingStrUtils(String content){
     *       String matchWord = "{{*http://|https://}}`+";
     *       int[] region={0,0};
     *       int foundCount=0;
     *       StringReader sr = new StringReader(content);
     *       BufferedReader bur = new BufferedReader(sr);
     *       String line;
     *       try {
     *           while((line = bur.readLine()) != null){
     *               while(region != null){
     *                   region = StrUtil.regionIndexWithWildCard(
     *                       matchWord, line, false, region[1]
     *                       ,null, StrUtil.IGNORE_BRACKETS, null);
     *                   if(region != null){
     *                       foundCount++;
     *                       System.out.println(foundCount
     *                           + " : "
     *                           + StrUtil.substring(line, region));
     *                   }
     *               }
     *               region = new int[2];
     *           }
     *           bur.close();
     *           sr.close();
     *       } catch (IOException e) {
     *           e.printStackTrace();
     *       }
     *       System.out.println("**** found URLs count:"+foundCount);
     *   }
     *
     * -----------
     * Example-2:
     *   // [NOT-PATTERN] -------------------------------------
     *   // code: アンカータグのHREF属性で "http://" でも "https://" でもないプロトコルで始まる記述を探す。その１
     *   // code: NOT ("http://" or "https://") , pattern-1
     *     String content = "<PRE>any text</PRE><A HREF=\"ftp://fileserver/file1.zip\">file1.zip</A>";
     *     int[] region = StrUtil.regionIndexWithWildCard( "<A HREF=\"{{!http://|https://}}fileserver/`+", content, true, 0);
     *
     *   // output: ここでは "ftp://" を発見した。
     *   // output: found "ftp://".
     *   //   region = {19,53}
     *   //   "[" + StrUtil.substring(content, region) + "]" = [<A HREF="ftp://fileserver/file1.zip"]
     *
     *   // [NOT-PATTERN] -------------------------------------
     *   // code: アンカータグのHREF属性で "http://" でも "https://" でもないプロトコルで始まる記述を探す。その２
     *   // 　　　検索するマッチング文字列後半が若干異なる。
     *   // code: NOT ("http://" or "https://")  , pattern-2
     *     String content = "<PRE>any text</PRE><A HREF=\"ftp://fileserver/file1.zip\">file1.zip</A>";
     *     int[] region = StrUtil.regionIndexWithWildCard( "<a href=\"{{!http://|https://}}`+", content, true, 0);
     *
     *   // output: ここでは "ftp://" を発見した。
     *   // output: found "ftp://".
     *   //   region = {19,53}
     *   //   "[" + StrUtil.substring(content, region) + "]" = [<A HREF="ftp://fileserver/file1.zip"]
     *
     *   // [NOT-PATTERN] -------------------------------------
     *   // code: アンカータグのHREF属性で "http://" でも "https://" でもないプロトコルで始まる記述を探す。その３
     *   // code: NOT ("http://" or "https://")  , pattern-3
     *     String content = "<PRE>any text</PRE><A HREF=\"ftp://fileserver/file1.zip\">file1.zip</A>";
     *     int[] region = StrUtil.regionIndexWithWildCard( "{{!http://|https://}}fileserver/`+", content, true, 0);
     *
     *   // output: 一応 "fileserver/" 以降を引っ掛けてはいるが、対象文字列の先頭の "<PRE>" がすでに
     *   // 　　　　"http://" でも "https://" でもない。このように対象文字列によって結果が異なるのでお奨めしない。
     *   // 　　　　確実なポイントは、NOTコマンド開始 "{{!" より前に、"<A HREF=\"" などの固定文字列を置くこと。
     *   // output: it is incorrect.
     *   //   region = {19,53}
     *   //   "[" + StrUtil.substring(content, region) + "]" = [<PRE>any text</PRE><A HREF="ftp://fileserver/file2.zip"]
     *
     *   // [OR-PATTERN] -------------------------------------
     *   // code: "ftp://" か "http://" か "https://" のいずれかが含まれるURLの記述を探す。
     *   // code: find URL-pattern including "ftp://" or "http://" or "https://".
     *    String content = "<a href=\"https://www.site.org/foo.cgi?bar=123>Link</a>";
     *    int[] region = StrUtil.regionIndexWithWildCard( "{{*ftp://|http://|https://}}`+", content, true, 0);
     *
     *   // output: "https://" を発見。
     *   // output: found "https://".
     *   //   region = {9,44}
     *   //   "[" + StrUtil.substring(content, region) + "]" = [https://www.site.org/foo.cgi?bar=123]
     *
     * </pre>
     *
     * @param keyword ワイルドカードを指定できる検索文字列
     * @param value 比較対象文字列
     * @param ignoreCase 大文字小文字を無視するならtrue
     * @param startIndex 引数value中の文字検索開始インデックス
     * @return マッチングしたらそのマッチング範囲の最初と最後尾のインデックスをint型配列で返す。マッチングしなければnullを返す。
     *
     * @see matchWithWildCard(String matchWord, String value, boolean ignoreCase)
     */
     public static int[] regionIndexWithWildCard(String keyword, String value,
             boolean ignoreCase, int startIndex ) {
         if (isEmpty(keyword) || isEmpty(value)) {
             return null;
         }
         if (startIndex >= value.length()) {
             throw new ArrayIndexOutOfBoundsException("\n\t startIndex = " + startIndex
                     + ": but value.length = " + value.length() + ".");
         }


         int[] result = { 0, 0 };
         String[] matchWords = splitByWildCard(keyword, null, null, null);
         if (ignoreCase) {
             value = value.toLowerCase();
             for (int i = 0; i < matchWords.length; i++) {
                 matchWords[i] = matchWords[i].toLowerCase();
             }
         }

         boolean isContainWildCardAsAnyChar = false; // ex. "@LPHABET:%{{*choice1|choice2}}"
         int wordListIndex = 0;
         int posValue = startIndex;
         while (posValue < value.length() || wordListIndex < matchWords.length) {
             // MatchWordリストが残っているのにvalue末尾に達した
             if (wordListIndex < matchWords.length && posValue >= value.length()) {
                 return null;
             }
             // value評価中だがMatchWordリスト末尾までエラー無し
             if (/*posValue < value.length() &&*/ wordListIndex >= matchWords.length) {
                 result[0] = startIndex;
                 result[1] = posValue - 1;
                 return result;
             }

             String mw = matchWords[wordListIndex];
             char val = value.charAt(posValue);
             char mwChar;

             // 分割マッチワード種別：0=通常文字、1=長いワイルドカード、2=短いワイルドカード
             int matchWordClass = CLASS_NOT_WILDCARDS;
             if (contains(WILDCARDS_LONG, mw, false)) {
                 matchWordClass = CLASS_WILDCARDS_LONG;
             } else if (mw.length() == 1 && contains(WILDCARDS_SHORT, mw.charAt(0))) {
                 matchWordClass = CLASS_WILDCARDS_SHORT;
             }

             if (matchWordClass == CLASS_NOT_WILDCARDS) {
                 // 分割マッチワードが通常文字列だった
                 int indexBS = mw.indexOf("\\");
                 if (indexBS > -1 && !mw.equals("\\")) {
                     mw = remove(mw, "\\", false);
                 }

                 // マッチワード先頭の語なら、その語をvalue中から事前検索する
                 if (wordListIndex == 0) {
                     int preSearch = value.indexOf(mw, posValue);
                     if (preSearch > -1) {
                         startIndex = preSearch;
                         posValue = preSearch + mw.length();
                         wordListIndex++;
                     } else {
                         return null;
                     }
                 } else {
                     if (value.startsWith(mw, posValue)) {
                         // ２語目以降も一致
                         posValue = posValue + mw.length();
                         wordListIndex++;
                     } else {
                         wordListIndex = 0; continue;
                     }
                 }
             }

             // 分割マッチワードがワイルドカードだった
             else if (matchWordClass == CLASS_WILDCARDS_LONG) {
                 if (mw.equals(WILDCARDS_LONG[ANY_BLANKs])) {
                     if (isBlankAny(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isBlankAny(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[ANY_NUMBERs])) {
                     if (isNumber(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isNumber(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[ANY_ALPHABETs])) {
                     if (isAlphabet(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isAlphabet(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[CRs_OR_LFs])) {
                     if (isCRLF(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isCRLF(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[SPACEs_OR_TABs])) {
                     if (isSpaceOrTab(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isSpaceOrTab(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[ALPHAs_NUMs_BARs])) {
                     if (isNumAlphaBar(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isNumAlphaBar(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[URL_ELEMENTs])) {
                     if (isElementOfURL(value.charAt(posValue))) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         while (posValue < value.length()) {
                             if (isElementOfURL(value.charAt(posValue))) {
                                 posValue++;
                             } else {
                                 break;
                             }
                         }
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mw.equals(WILDCARDS_LONG[COMMAND_OPEN])) {
                     // コマンド開始 "{{"
                     if (matchWords[wordListIndex + 1].length() != 1
                             || !contains(COMMANDS, matchWords[wordListIndex + 1].charAt(0))) {
                         throw new IllegalArgumentException(
                                 "\n\t Found command-opener: \"{{\" but command not exist."
                                         + "\n\t Matching words = "
                                         + toStringFromArray(matchWords, false)
                                         + "\n\t Found array-index in above = "
                                         + wordListIndex
                                         + "\n\t Current position in target value = "
                                         + posValue
                                         + "\n\t NOTICE: Need appropriate command(ex.'*', '!') at next of \"{{\".");
                     }
                     if (arrayIndexOf(WILDCARDS_LONG[COMMAND_CLOSE], matchWords, false, wordListIndex + 2) == -1) {
                         throw new IllegalArgumentException(
                                 "\n\t Can not find command-closer: \"}}\" after command-opener: \"{{\"."
                                         + "\n\t Matching words = "
                                         + toStringFromArray(matchWords, false)
                                         + "\n\t NOTICE: Need command-closer for command-opener.");
                     }
                    //  if (!equalsWhichOf(exceptChars, '|')) {
                    //      throw new IllegalArgumentException(
                    //              "\n\t Excepted-Char-List must contain '|' when you using commands."
                    //                      + "\n\t Usage: regionIndexWithWildCardOf(String matchWord, String value, boolean ignoreCase, int startIndex, char[] quotes, char[] brackets, char[] exceptChars)"
                    //                      + "\n\t        Using argument : 'char[] exceptChars'");
                    //  }
                     char command = matchWords[++wordListIndex].charAt(0);
                     // コマンド範囲取得
                     String[] commandsRegion = regionArray(matchWords,
                             WILDCARDS_LONG[COMMAND_CLOSE], false, ++wordListIndex, -1);
                     // コマンド範囲サイズ取得('}}'までなので+1する)
                     int commandsRegionLength = commandsRegion.length + 1;
                     commandsRegion = removeFromArray(commandsRegion, "|", false);
                     if (commandsRegion.length < 2) {
                         throw new IllegalArgumentException(
                                 "\n\t OR-command format error. length of command-list is 1."
                                         + "\n\t Matching words = "
                                         + toStringFromArray(matchWords, false));
                     }
                     // コマンド範囲の直後、次のマッチングワードに関する情報セット
                     int nextWordListIndex = wordListIndex + commandsRegionLength;
                     String nextMatchWord = null;
                     if (nextWordListIndex < matchWords.length) {
                         nextMatchWord = matchWords[nextWordListIndex];
                     }
                     boolean isWildCardNextMatchWord = false;

                     // コマンド範囲取得完了。マッチング開始
                     boolean isMatched = false;
                     String matchedWord = null;
                     int matchedWordLength = 0;
                     java.util.Arrays.sort(commandsRegion);
                     int matchedIndex = -1;

                     // コマンド範囲にマッチするものがあるかどうか
                     for (int i = 0; i < commandsRegion.length; i++) {
                         matchedWord = commandsRegion[i];
                         matchedWordLength = matchedWord.length();

                         matchedIndex = value.indexOf(matchedWord, posValue);
                         if (matchedIndex > -1) {
                             isMatched = true;
                             break;
                         }
                     }
                     if (command == COMMANDS[CMD_OR]) {
                         if (isMatched) {
                             if (nextMatchWord == null) {
                                 if(isContainWildCardAsAnyChar){
                                     matchedIndex = 0;
                                 }
                                 result[0] = matchedIndex;
                                 result[1] = value.length() - 1;
                                 return result;
                             }
                             // OR判定でどれか存在した
                             wordListIndex = nextWordListIndex;
                             // 次のマッチワードがワイルドカードか判定フラグをセット
                             isWildCardNextMatchWord = (
                                     contains(WILDCARDS_LONG, nextMatchWord, false))
                                  || (nextMatchWord.length() == 1
                                          && contains(WILDCARDS_SHORT, nextMatchWord.charAt(0)));

                             int nextIndex = -1;
                             if (!isWildCardNextMatchWord) {
                                 // 次のマッチワードは通常文字
                                 nextIndex = value.indexOf(matchWords[wordListIndex], matchedIndex
                                         + matchedWordLength);
                                 if (nextIndex > -1) {
                                     posValue = nextIndex;
                                 } else {
                                     return null;
                                 }
                             } else {
                                 // 次のマッチワードはWildCard
                                 posValue = matchedIndex + matchedWordLength;
                             }
                             startIndex = matchedIndex;
                             // マッチした
                             continue;
                         } else {
                             // どれもマッチしない
                             return null;
                         }

                     // } else if (command == COMMANDS[CMD_NOT]) {
                     } else {
                         // command == COMMANDS[CMD_NOT]
                         if (isMatched) {
                             // OR判定でどれか存在した
                             return null;
                         } else {
                             // どれもマッチしない
                             // NOTでどれもマッチしないのはOK");
                             if (nextMatchWord == null) {
                                 result[0] = posValue;
                                 result[1] = value.length() - 1;
                                 return result;
                             }
                             // 次のマッチングワードをvalue中から検索する
                             isWildCardNextMatchWord = (
                                     contains(WILDCARDS_LONG, nextMatchWord, false))
                                     || (nextMatchWord.length() == 1
                                         && contains(WILDCARDS_SHORT, nextMatchWord.charAt(0)));

                             int nextPosValue = -1;
                             wordListIndex = nextWordListIndex;
                             if (!isWildCardNextMatchWord) {
                                 // 次のマッチワードは通常文字
                                 nextPosValue = value.indexOf(matchWords[nextWordListIndex],
                                         posValue);
                                 if (nextPosValue > -1) {
                                     posValue = nextPosValue;
                                     continue;
                                 } else {
                                     return null;
                                 }
                             } else {
                                 // 次のマッチワードはWildCard
                                 continue;
                             }
                         }
                     }
                 } else {
                     throw new IllegalArgumentException(
                             "\n\t Illegal Argument."
                                     + "\n\t Matching words = "
                                     + toStringFromArray(matchWords, false)
                                     + "\n\t Value = "
                                     + value
                     );
                 }
             //} else if (matchWordClass == CLASS_WILDCARDS_SHORT) {
             } else {
                 // matchWordClass == CLASS_WILDCARDS_SHORT
                 mwChar = mw.charAt(0);
                 if (mwChar == WILDCARDS_SHORT[ANY_BLANK]) {
                     if (isBlankAny(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[ANY_NUMBER]) {
                     if (isNumber(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[ANY_ALPHABET]) {
                     if (isAlphabet(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[CR_OR_LF]) {
                     if (isCRLF(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[SPACE_OR_TAB]) {
                     if (isSpaceOrTab(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[ALPHA_NUM_BAR]) {
                     if (isNumAlphaBar(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[URL_ELEMENT]) {
                     if (isElementOfURL(val)) {
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         posValue++;
                         wordListIndex++;
                         continue;
                     } else {
                         wordListIndex = 0; posValue++; continue;
                     }
                 } else if (mwChar == WILDCARDS_SHORT[CHAR]) {
                     if (matchWords.length > 1 && wordListIndex == 0) {
                         startIndex = posValue;
                     }
                     posValue++;
                     wordListIndex++;
                     continue;
                 //} else if (mwChar == WILDCARDS_SHORT[ANY_WORD]) {
                 } else {
                 // mwChar == WILDCARDS_SHORT[ANY_WORD]
                     isContainWildCardAsAnyChar = true;
                     // MatchWordリスト中で、"%"の直後に連続したワイルドカードはエラーとする
                     if (wordListIndex < matchWords.length - 1) {
                         String nextMatchWord = matchWords[wordListIndex + 1];
                         if (nextMatchWord.length() < 2) {
                             if (contains(WILDCARDS_SHORT, nextMatchWord.charAt(0))) {
                                 throw new IllegalArgumentException("\n\t Found wild-card: '"
                                         + nextMatchWord + "' next of '%'."
                                         + "\n\t MatchWord    = [" + keyword + "]"
                                         + "\n\t Taeget value = [" + value + "]"
                                         + "\n\t Current index in target value = " + posValue
                                         + "\n\t NOTICE: Can not use any wild-card next of '%'.");
                             }
                         } else {
                             if (contains(WILDCARDS_LONG, nextMatchWord, false)) {
                                 // '%'の後ろでコマンド開始 "{{" は許可する
                                 if (!nextMatchWord.equals(WILDCARDS_LONG[COMMAND_OPEN])) {
                                     throw new IllegalArgumentException("\n\t Found wild-card: '"
                                             + nextMatchWord + "' next of '%'."
                                             + "\n\t MatchWord    = [" + keyword + "]"
                                             + "\n\t Taeget value = [" + value + "]"
                                             + "\n\t Current index in target value = " + posValue
                                             + "\n\t NOTICE: Can not use any wild-card next of '%'.");
                                 } else {
                                     // コマンドだった
                                     wordListIndex++;
                                     posValue++;
                                     continue;
                                 }
                             }
                         }
                         // ワイルドカードでなければ、次のマッチングワードまでスキップ
                         nextMatchWord = remove(nextMatchWord, "\\", false);
                         if (wordListIndex == 0) {
                             startIndex = posValue;
                         }
                         int i = value.indexOf(nextMatchWord, posValue);
                         if (i < 0) {
                             return null;
                         }
                         posValue = i + nextMatchWord.length();
                         wordListIndex += 2;
                         continue;
                  // } else if (wordListIndex == matchWords.length - 1) {
                     } else {
                         // マッチワード末尾の'%'の場合
                         posValue += value.substring(posValue).length();
                         break;
                     }
                 }
             }
         } // [end of while]

         if(isContainWildCardAsAnyChar){
             startIndex = 0;
         }
         result[0] = startIndex;
         result[1] = posValue - 1;
         return result;
     }


    /**
     * <pre>
     * WildCardを用いた文字列比較をおこなう。
     * splitByWildCard()を用い、内部処理オプションとして
     * デフォルトのクォート文字配列・括弧文字配列・例外文字配列を用いる。
     * オプション文字配列に含まれる文字が多数含まれる、大きな文字列データに対して
     * マッチングをおこなう際は、オプション文字配列をカラにするとより高速にマッチングをおこなう。
     *
     *
     * WildCard expression:
     *   "_" = a char
     *   "$" = a space or tab
     *   "~" = a CR or LF
     *   "&" = a space, tab, CR and LF
     *   "#" = a '0'-'9'
     *   "@" = a 'A'-'Z', 'a'-'z'
     *   "?" = a number or alphabet or '-' or '_'
     *   "`" = a URL string
     *
     *   "%"  = any chars (containing spaces/tabs/CRs/LFs)
     *   "$+" = spaces or tabs
     *   "~+" = CRs or LFs
     *   "&+" = spaces, tabs, CRs and LFs
     *   "#+" = region of numbers
     *   "@+" = region of alphabets
     *   "?+" = numbers or alphabets or '-' or '_'
     *   "`+" = URL strings
     *     ex. URL-string is not containing '"', '\'', '\r', '\n', '\t', ' ', '<', '>', '(', ')'.
     *
     *   "{{" - "}}" = region of 'COMMAND'
     *     ex. "{{*" - "}}" = command 'OR'
     *         "{{!" - "}}" = command 'NOT and OR'
     *
     *         "{{*http://|https://}}" = "http://" OR "https://"
     *         "{{!http://|https://}}" = NOT("http://" AND "https://")
     *
     * Escaped char expression:
     *   "\_"    = "_"  as String-Data
     *   "\$"    = "$"  as String-Data
     *   "\~"    = "~"  as String-Data
     *   "\&"    = "&"  as String-Data
     *   "\#"    = "#"  as String-Data
     *   "\%"    = "%"  as String-Data
     *   "\?"    = "?"  as String-Data
     *   "\`"    = "`"  as String-Data
     *   "\$+"   = "$+" as String-Data
     *   "\!+"   = "!+" as String-Data
     *   "\&+"   = "&+" as String-Data
     *   "\#+"   = "#+" as String-Data
     *   "\@+"   = "@+" as String-Data
     *   "\?+"   = "?+" as String-Data
     *   "\`+"   = "`+" as String-Data
     *
     * Option char expression:
     * 　Default quote list         : ["], [']
     * 　Default bracket list       : "(", "[", "<", "{", "}", ">", "]", ")"
     * 　Default excepted-char list : "=", "|"
     *
     *
     * Example:
     *   StrUtil.matchWithWildCard( "_"              , "A"             ,false) = true
     *   StrUtil.matchWithWildCard( "_"              , "AA"            ,false) = true
     *   StrUtil.matchWithWildCard( "__"             , "A"             ,false) = false
     *   StrUtil.matchWithWildCard( "01_"            , "01A"           ,false) = true
     *   StrUtil.matchWithWildCard( "01A_"           , "01A"           ,false) = false
     *   StrUtil.matchWithWildCard( "________"       , "0123ABCD"      ,false) = true
     *   StrUtil.matchWithWildCard( "A Wil_ Car_."   , "A Wild Card."  ,false) = true
     *   StrUtil.matchWithWildCard( "a wil_ car_."   , "A Wild Card."  ,true ) = true
     *   StrUtil.matchWithWildCard( "a wil_ car_."   , "A Wild Card."  ,false) = false
     *   StrUtil.matchWithWildCard( "a wild card_"   , "A Wild Card."  ,true ) = true
     *   StrUtil.matchWithWildCard( "a wild card_"   , "A Wild Card!!" ,true ) = true
     *   StrUtil.matchWithWildCard( "a wild card__"  , "A Wild Card!!" ,true ) = true
     *   StrUtil.matchWithWildCard( "a wild card___" , "A Wild Card!!" ,true ) = false
     *
     *   StrUtil.matchWithWildCard( "A Space$Or$Tab"   , "A Space Or\tTab"    ,false) = true
     *   StrUtil.matchWithWildCard( "A Space$$$Or$Tab" , "A Space   Or\tTab"  ,false) = true
     *   StrUtil.matchWithWildCard( "A Space$Or$Tab"   , "A Space   Or\tTab"  ,false) = false
     *   StrUtil.matchWithWildCard( "A Space$Or$Tab"   , "A Space Or\t\tTab"  ,false) = false
     *   StrUtil.matchWithWildCard( "A Space$Or$$Tab"  , "A Space Or\t\tTab"  ,false) = true
     *   StrUtil.matchWithWildCard( "A Space$Or$Tab"   , "A Space\nOr\rTab"   ,false) = false
     *   StrUtil.matchWithWildCard( "A Space Or Tab$"  , "A Space Or Tab "    ,false) = true
     *   StrUtil.matchWithWildCard( "A Space Or Tab$"  , "A Space Or Tab \t"  ,false) = true
     *   StrUtil.matchWithWildCard( "A Space Or Tab$$" , "A Space Or Tab "    ,false) = false
     *   StrUtil.matchWithWildCard( "a sPACE oR TAB$"  , "A Space Or Tab\t\t" ,true ) = true
     *
     *   StrUtil.matchWithWildCard( "Spaces$+Or$+Tabs"      , "Spaces Or Tabs"           ,false) = true
     *   StrUtil.matchWithWildCard( "Spaces$+Or$+Tabs"      , "Spaces  Or  Tabs"         ,false) = true
     *   StrUtil.matchWithWildCard( "Spaces$+Or$+Tabs"      , "Spaces  \t  Or  \t  Tabs" ,false) = true
     *   StrUtil.matchWithWildCard( "Spaces$+$+Or$+Tabs"    , "Spaces  Or  Tabs"         ,false) = false
     *   StrUtil.matchWithWildCard( "Spaces Or Tabs$+"      , "Spaces Or Tabs \t \t \t"  ,false) = true
     *   StrUtil.matchWithWildCard( "Spaces Or Tabs$+$+"    , "Spaces Or Tabs \t \t \t"  ,false) = false
     *   StrUtil.matchWithWildCard( "$+Spaces$+Or$+Tabs"    , "   Spaces Or Tabs"        ,false) = true
     *   StrUtil.matchWithWildCard( "\t$$$Spaces$+Or$+Tabs" , "\t   Spaces Or Tabs"      ,false) = true
     *   StrUtil.matchWithWildCard( "\t$$$spaces$+or$+tabs" , "\t   Spaces Or Tabs"      ,false) = false
     *   StrUtil.matchWithWildCard( "\t$$$spaces$+or$+tabs" , "\t   Spaces Or Tabs"      ,true ) = true
     *
     *   StrUtil.matchWithWildCard( "CR or LF ~ / ~"        , "CR or LF \r / \n"        ,false) = true
     *   StrUtil.matchWithWildCard( "CR or LF ~~ / ~"       , "CR or LF \r\n / \n"      ,false) = true
     *   StrUtil.matchWithWildCard( "CR or LF ~ / ~~"       , "CR or LF \r / \n\r"      ,false) = true
     *   StrUtil.matchWithWildCard( "CRLF at End Of Line~~" , "CRLF at End Of Line\n\r" ,false) = true
     *   StrUtil.matchWithWildCard( "CRLF at End Of Line~"  , "CRLF at End Of Line\n\r" ,false) = true
     *   StrUtil.matchWithWildCard( "CRLF at End Of Line~~" , "CRLF at End Of Line\n"   ,false) = false
     *
     *   StrUtil.matchWithWildCard( "CRs and LFs~+"       , "CRs and LFs\r\n\r\n"         ,false) = true
     *   StrUtil.matchWithWildCard( "CRs and LFs~~+"      , "CRs and LFs\r\n\r\n"         ,false) = true
     *   StrUtil.matchWithWildCard( "CRs and LFs~~~+"     , "CRs and LFs\r\n\r\n"         ,false) = true
     *   StrUtil.matchWithWildCard( "CRs and LFs~~~~+"    , "CRs and LFs\r\n\r\n"         ,false) = true
     *   StrUtil.matchWithWildCard( "CRs and LFs~~~~~+"   , "CRs and LFs\r\n\r\n"         ,false) = false
     *   StrUtil.matchWithWildCard( "~+CRs and LFs~+"     , "\r\n\r\nCRs and LFs\r\n\r\n" ,false) = true
     *   StrUtil.matchWithWildCard( "~~~+CRs and LFs~+"   , "\r\n\r\nCRs and LFs\r\n\r\n" ,false) = true
     *   StrUtil.matchWithWildCard( "~~~~+CRs and LFs~+"  , "\r\n\r\nCRs and LFs\r\n\r\n" ,false) = true
     *   StrUtil.matchWithWildCard( "~~~~~+CRs and LFs~+" , "\r\n\r\nCRs and LFs\r\n\r\n" ,false) = false
     *
     *   StrUtil.matchWithWildCard( "Space,Tab,CR,LF [&&&&]"  , "Space,Tab,CR,LF [ \t\r\n]"  ,false) = true
     *   StrUtil.matchWithWildCard( "Space,Tab,CR,LF [&+]"    , "Space,Tab,CR,LF [ \t\r\n]"  ,false) = true
     *   StrUtil.matchWithWildCard( "Space,Tab,CR,LF [&&+]"   , "Space,Tab,CR,LF [ \t\r\n]"  ,false) = true
     *   StrUtil.matchWithWildCard( "Space,Tab,CR,LF [&+&]"   , "Space,Tab,CR,LF [ \t\r\n]"  ,false) = false
     *   StrUtil.matchWithWildCard( "Space,CR&+[&+]"          , "Space,CR        [ \r]"      ,false) = true
     *   StrUtil.matchWithWildCard( "Space,CR&+[$&+]"         , "Space,CR        [ \r]"      ,false) = true
     *   StrUtil.matchWithWildCard( "Space,CR&+[$~]"          , "Space,CR        [ \r]"      ,false) = true
     *   StrUtil.matchWithWildCard( "Space,CR&+[~+]"          , "Space,CR        [ \r]"      ,false) = false
     *   StrUtil.matchWithWildCard( "Space,CR&+[$+]"          , "Space,CR        [ \r]"      ,false) = false
     *
     *   StrUtil.matchWithWildCard( "NumberCheck:#"     , "NumberCheck:4"       ,false) = true
     *   StrUtil.matchWithWildCard( "NumberCheck:#"     , "NumberCheck:45"      ,false) = true
     *   StrUtil.matchWithWildCard( "NumberCheck:##"    , "NumberCheck:4"       ,false) = false
     *   StrUtil.matchWithWildCard( "NumberCheck:####"  , "NumberCheck:0123"    ,false) = true
     *   StrUtil.matchWithWildCard( "NumberCheck:####"  , "NumberCheck:0123456" ,false) = true
     *   StrUtil.matchWithWildCard( "NumberCheck:#####" , "NumberCheck:0123"    ,false) = false
     *   StrUtil.matchWithWildCard( "numbercheck:####"  , "NumberCheck:0123"    ,true ) = true
     *   StrUtil.matchWithWildCard( "numbercheck:####"  , "NumberCheck:0123"    ,false) = false
     *
     *   StrUtil.matchWithWildCard( "Numbers:#+"       , "Numbers:123"          ,false) = true
     *   StrUtil.matchWithWildCard( "Numbers:##+"      , "Numbers:123"          ,false) = true
     *   StrUtil.matchWithWildCard( "Numbers:#+#"      , "Numbers:123"          ,false) = false
     *   StrUtil.matchWithWildCard( "UserID=A#+-Z#+"   , "UserID=A45-Z9876"     ,false) = true
     *   StrUtil.matchWithWildCard( "UserID=A##-Z#+"   , "UserID=A45-Z9876"     ,false) = true
     *   StrUtil.matchWithWildCard( "UserID=A#+-Z####" , "UserID=A45-Z9876"     ,false) = true
     *   StrUtil.matchWithWildCard( "TEL:+81-##-#+-#+" , "TEL:+81-03-1234-5678" ,false) = true
     *
     *   StrUtil.matchWithWildCard( "Alphabet=A@"     , "Alphabet=AZ"     ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabet=@Z"     , "Alphabet=AZ"     ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabet=@@"     , "Alphabet=AZ"     ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabet=ABC@@@" , "Alphabet=ABCXYZ" ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabet=@1@2@3" , "Alphabet=A1B2C3" ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabet=@1@2@3" , "Alphabet=A1b2c3" ,false) = true
     *   StrUtil.matchWithWildCard( "aLpHaBeT=@1@2@3" , "Alphabet=a1B2c3" ,true ) = true
     *   StrUtil.matchWithWildCard( "aLpHaBeT=@1@2@3" , "alphabet=a1b2C3" ,false) = false
     *
     *   StrUtil.matchWithWildCard( "Alphabets=@+1200"      , "Alphabets=TZR1200"   ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@+"    , "Alphabets=HF456-ZXY" ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@@+"   , "Alphabets=HF456-ZXY" ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@@@+"  , "Alphabets=HF456-ZXY" ,false) = true
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@@@@+" , "Alphabets=HF456-ZXY" ,false) = false
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@+@"   , "Alphabets=HF456-ZXY" ,false) = false
     *   StrUtil.matchWithWildCard( "Alphabets=@@456-@+@+"  , "Alphabets=HF456-ZXY" ,false) = false
     *
     *   StrUtil.matchWithWildCard( "Any Chars %"  , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any Chars%"   , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any %"        , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any%"         , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any % ABC123" , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any%ABC123"   , "Any Chars ABC123" ,false) = true
     *   StrUtil.matchWithWildCard( "Any % ABC124" , "Any Chars ABC123" ,false) = false
     *   StrUtil.matchWithWildCard( "Any%ABC122"   , "Any Chars ABC123" ,false) = false
     *   StrUtil.matchWithWildCard( "Any % ABC%"   , "Any Chars ABC123" ,false) = true
     *
     *   StrUtil.matchWithWildCard("amount=\\$1##"                 , "Amount=$100"          , true ) = true
     *   StrUtil.matchWithWildCard("amount=\\\\100"                , "Amount=\\100"         , true ) = true
     *   StrUtil.matchWithWildCard("amount=\\\\100,$+\\\\#+"       , "Amount=\\100, \\200"  , true ) = true
     *   StrUtil.matchWithWildCard("Escape\\_Char\\~"              , "Escape_Char~"         , false) = true
     *   StrUtil.matchWithWildCard("@+,Orange\\&Pe@+"              , "Apple,Orange&Peach"   , false) = true
     *   StrUtil.matchWithWildCard("Percentage$+:&+##\\%"          , "Percentage  :  87%"   , false) = true
     *   StrUtil.matchWithWildCard("user\\_id\\@yourmail.@+"       , "user_id@yourmail.org" , false) = true
     *   StrUtil.matchWithWildCard("%.cgi\\?key#=value#\\&@+2=@+2" , "http://www.example.com/pl.cgi?key1=value1&key2=value2" , false) = true
     *   StrUtil.matchWithWildCard("\\# \\$+\\$+\\$ [comment is @+] \\$+\\$+\\$" , "# $+$+$ [comment is here] $+$+$" , false) = true
     *
     *   StrUtil.matchWithWildCard( "? or NUM?",  "A or NUM0",     false) = true
     *   StrUtil.matchWithWildCard( "? or NUM?",  "A1 or NUM0",    false) = true // --> match: "1 or NUM0"
     *   StrUtil.matchWithWildCard( "?? or NUM?", "A1 or NUM-",    false) = true
     *   StrUtil.matchWithWildCard( "?+ or NUM?", "A1 or NUM_",    false) = true
     *   StrUtil.matchWithWildCard( "?+ / ?+",    "ALPHA / 01234", false) = true
     *   StrUtil.matchWithWildCard( "?+ / ?+",    "ALPHA/01234",   false) = false
     *
     *   StrUtil.matchWithWildCard( "http://`",    "http://(" ,false)  = true
     *   StrUtil.matchWithWildCard( "http://`",    "http://'" ,false)  = true
     *   StrUtil.matchWithWildCard( "http://`",    "http://<" ,false)  = false
     *   StrUtil.matchWithWildCard( "http://`",    "http:// " ,false)  = false
     *   StrUtil.matchWithWildCard( "http://`",    "http://\n" ,false) = false
     *   StrUtil.matchWithWildCard( "http://`",    "http://\r" ,false) = false
     *   StrUtil.matchWithWildCard( "http://`",    "http://\t" ,false) = false
     *   StrUtil.matchWithWildCard( "http://`+/A_`name`/", "http://site/A_<name>/" ,false) = false
     *   StrUtil.matchWithWildCard( "http://`+/A_`name`/", "http://site/A_(name)/" ,false) = true
     *   // * URL-characters : see also RFC2396.
     *
     *   StrUtil.matchWithWildCard( "http://`+<`",    "http://site<a" ,false) = true
     *   StrUtil.matchWithWildCard( "http://`+<``",   "http://site<a" ,false) = false
     *   StrUtil.matchWithWildCard( "http://`+<name`/",    "http://site/A_<name}/" ,false) = true
     *
     * </pre>
     * @param keyword
     * @param value
     * @param ignoreCase
     * @return マッチしていればtrue、それ以外はfalse
     *
     */
    public static boolean matchWithWildCard(String keyword, String value, boolean ignoreCase) {
        return regionIndexWithWildCard(keyword, value, ignoreCase, 0) != null;
    }


    /**
     * <pre>
     * 文字列前後から余分な空白を除く。
     *
     * Example:
     *   StrUtil.trim("A BCD  ")           = "A BCD"
     *   StrUtil.trim(" AB CD ")           = "AB CD"
     *   StrUtil.trim("   ABC D")          = "ABC D"
     *   StrUtil.trim("\r\nAB C D\t")      = "AB C D"
     *   StrUtil.trim(" A B C D   ")       = "A B C D"
     *   StrUtil.trim("\n \tA B C D\t \n") = "A B C D"
     *   StrUtil.trim("")                  = ""
     *   StrUtil.trim(null)                = null
     *
     * </pre>
     * @param value
     * @return 文字列前後から余分な空白を除いた結果
     */
    public static String trim(String value) {
        return (value == null ? null : value.trim());
    }


    /**
     * <pre>
     * 文字列中に含まれる、すべての空白を取り除く。
     * 空白と判定される文字は、半角スペース、タブ、改行コード(\r, \n)となる。
     *
     * Example:
     *   StrUtil.trimAll("ABCD ")            = "ABCD"
     *   StrUtil.trimAll(" ABCD ")           = "ABCD"
     *   StrUtil.trimAll("  ABCD")           = "ABCD"
     *   StrUtil.trimAll("\r\nABC \tD")      = "ABCD"
     *   StrUtil.trimAll("  A B CD ")        = "ABCD"
     *   StrUtil.trimAll("\rA\nB\tC D ")     = "ABCD"
     *   StrUtil.trimAll("  \t\tAB CD\n\n ") = "ABCD"
     * </pre>
     *
     * @param value
     * @return 文字列中に含まれる、すべての空白を取り除いた結果
     */
    public static String trimAll(String value){
        String rtn = null;
        String trimmed = trim(value);
        if(trimmed != null){
            StringBuilder sb = new StringBuilder();
            char[] values = trimmed.toCharArray();
            int index = 0;
            while(index < values.length){
                if( isBlankAny(values[index]) ){
                    index++;
                    continue;
                }
                sb.append(values[index]);
                index++;
            }
            rtn = sb.toString();
        }
        return rtn;
    }


    /**
     * <pre>
     * 文字列の先頭にある空白を取り除く。
     *
     * Example:
     *   StrUtil.trimLeft("   ABC   ")                 = "ABC   "
     *   StrUtil.trimLeft("   A B C   ")               = "A B C   "
     *   StrUtil.trimLeft("   A\tB\tC\t   ")           = "A\tB\tC\t   "
     *   StrUtil.trimLeft("\r\nABC\r\nABC")            = "ABC\r\nABC"
     *   StrUtil.trimLeft("\t\n\n\r\r A B C   \t\r\n") = "A B C   \t\r\n"
     *
     * </pre>
     * @param value
     * @return 先頭の空白を取り除いた文字列
     */
    public static String trimLeft(String value){
        String rtn = null;
        if(!isEmpty(value)){
            int pos = indexOfNextChar(value, 0);
            rtn = value.substring(pos);
        }else{
            rtn = value;
        }
        return rtn;
    }


    /**
     * <pre>
     * 文字列の末尾にある空白を取り除く。
     *
     * Example:
     *   StrUtil.trimRight("   ABC   ")                 = "   ABC"
     *   StrUtil.trimRight("  A B C   ")                = "  A B C"
     *   StrUtil.trimRight("    A\tB\tC\t   ")          = "    A\tB\tC"
     *   StrUtil.trimRight("ABC\r\nABC\r\n")            = "ABC\r\nABC"
     *   StrUtil.trimRight("\t\t\n\n\r\r A B C \t\r\n") = "\t\t\n\n\r\r A B C"
     *
     * </pre>
     * @param value
     * @return 先頭の空白を取り除いた文字列
     */
    public static String trimRight(String value){
        String rtn = null;
        if(!isEmpty(value)){
            int pos = lastIndexOfPrevChar(value, 0);
            rtn = value.substring(0, pos+1);
        }else{
            rtn = value;
        }
        return rtn;
    }


    /**
     * <pre>
     * 第１引数で与えられる検索文字列が、第二引数で与えられる文字列の先頭にあるかどうかのマッチングをおこない、
     * 先頭の文字列がマッチングした場合は、マッチング部分を削除した残りを結果として返す。
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     *
     * Example:
     *   StrUtil.trimLeftMatch("SELECT", "SELECT * from TABLE;", true)        = "* from TABLE;"
     *   StrUtil.trimLeftMatch("select", "SELECT   *  from TABLE; ", true)    = "*  from TABLE;"
     *   StrUtil.trimLeftMatch("SELECT", "SELECT * from TABLE;", false)       = "* from TABLE;"
     *   StrUtil.trimLeftMatch("select", "SELECT   *  from TABLE; ", false)   = null
     *   StrUtil.trimLeftMatch("  select", "SELECT   *  from TABLE; ", true)  = null
     *   StrUtil.trimLeftMatch("select", "  SELECT   *  from TABLE; ", true)  = null
     *   StrUtil.trimLeftMatch("", "", true)      = null
     *   StrUtil.trimLeftMatch("", null, true)    = null
     *   StrUtil.trimLeftMatch(null, "", true)    = null
     *   StrUtil.trimLeftMatch(null, null, true)  = null
     *   StrUtil.trimLeftMatch("", "", false)     = null
     *   StrUtil.trimLeftMatch("", null, false)   = null
     *   StrUtil.trimLeftMatch(null, "", false)   = null
     *   StrUtil.trimLeftMatch(null, null, false) = null
     *
     * </pre>
     * @param keyword
     * @param value
     * @param ignoreCase
     * @return マッチング部分を削除した結果文字列
     */
    public static String trimLeftMatch(String keyword, String value, boolean ignoreCase){
        String rtn = null;
        if(isEmpty(value) || isEmpty(keyword)){
            return rtn;
        }
        if( value.length() < keyword.length() ){
            return rtn;
        }
        String chkValue = value;
        String chkSubstringWord = keyword;
        if(ignoreCase){
            chkValue = chkValue.toLowerCase();
            chkSubstringWord = keyword.toLowerCase();
        }
        if(chkValue.startsWith(chkSubstringWord)){
            rtn = trim(value.substring(keyword.length(), value.length()));
        }
        return rtn;
    }


    /**
     * <pre>
     * 引数１で与えられる文字列中に、引数２で指定される文字列が存在したら、
     * 引数１の文字列から引数２の文字列に該当する部分を削除する。
     *
     *
     * Example:
     *   StrUtil.remove("ABCD1234ABCD1234", "1234",       false) = "ABCDABCD"
     *   StrUtil.remove("ABCD1234ABCD1234", "ABCD",       false) = "12341234"
     *   StrUtil.remove("ABCD1234ABCD1234", "abcd",       false) = "ABCD1234ABCD1234"
     *   StrUtil.remove("ABCD1234ABCD1234", "abcd",        true) = "12341234"
     *   StrUtil.remove("ＡＢAＡCD1234**ＡＡ", "aａ",      true) = "ＡＢCD1234**ＡＡ"
     *   StrUtil.remove("A B   C D   あ い う  Ａ ", " ",  true) = "ABCDあいうＡ"
     *   StrUtil.remove("", null, true)                          = ""
     *   StrUtil.remove(null, "", true)                          = null
     *   StrUtil.remove(null, null, true)                        = null
     *
     * </pre>
     * @param value 削除対象文字列
     * @param keyword 削除したい文字列
     * @param ignoreCase 大文字小文字を無視したいならtrue、判定したいならfalse
     * @return 削除したい文字列が削除された結果
     */
    public static String remove(String value, String keyword, boolean ignoreCase){
        if(isEmpty(value) || isEmpty(keyword) ){
            return value;
        }
        int valueLen = value.length();
        int removeWordLen = keyword.length();
        int begin = 0;
        int next = 0;
        StringBuilder rtn = new StringBuilder();

        if(ignoreCase){
            String lowerValue = value.toLowerCase();
            String lowerRemoveWord = keyword.toLowerCase();
            while(true){
                next = lowerValue.indexOf(lowerRemoveWord, begin);
                if(next < 0){
                    break;
                }
                String tmp = value.substring(begin, next);
                rtn.append(tmp);
                begin = next + removeWordLen;
            }
        }else{
            while(true){
                next = value.indexOf(keyword, begin);
                if(next < 0){
                    break;
                }
                String tmp = value.substring(begin, next);
                rtn.append(tmp);
                begin = next + removeWordLen;
            }
        }
        if(begin < valueLen){
            rtn.append(value.substring(begin));
        }
        return rtn.toString();
    }


    /**
     * <pre>
     * 引数valueで与えられた文字列から、引数startIndexの位置から
     * 引数lengthの文字数分を削除する。
     *
     * Example:
     *   StrUtil.remove("0123456789A", 0, 0) = "0123456789A"
     *   StrUtil.remove("0123456789A", 0, 3) = "3456789A"
     *   StrUtil.remove("0123456789A", 1, 8) = "09A"
     *   StrUtil.remove("0123456789A", 6, 4) = "012345A"
     *   StrUtil.remove("0123456789A", 6, 5) = "012345"
     *   StrUtil.remove("0123456789A", 9, 1) = "012345678A"
     *   StrUtil.remove("0123456789A", 9, 0) = "0123456789A"
     *
     * </pre>
     * @param value
     * @param offset
     * @param length
     * @return 削除結果
     */
    public static String remove(String value, int offset, int length) {
        if(isEmpty(value)){
            return value;
        }
        if(offset >= value.length()){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t startIndex = " + offset + ": but value.length() = " + value.length()+ "."
                    );
        }
        if(offset + length > value.length()){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t startIndex + length = " + (offset + length)+ ": but value.length() = " + value.length()+ "."
                    );
        }
        StringBuilder rtn = new StringBuilder();
        if(offset > 0){
            rtn.append(value.substring(0, offset));
        }
        rtn.append(value.substring(offset + length));
        return rtn.toString();
    }


    /**
     * <pre>
     * 引数valueで指定された文字列を囲んでいる1文字目と最後尾に
     * 引数encloserで指定される囲み文字がある場合、
     * その囲み文字を削除して返す。
     * 引数valueの対象文字列がnullか空なら、nullを返す。
     * 引数valueに含まれるencloser文字が、１文字目または最後尾のどちらかに
     * １つしか存在しない場合はそれを削除せず、第一引数の対象文字列をそのまま返す。
     *
     * Example:
     *   StrUtil.removeEncloser("\"a enclosed string\"", '"')  = [a enclosed string]
     *   StrUtil.removeEncloser("\"a enclosed string\"", '\'') = ["a enclosed string"]
     *   StrUtil.removeEncloser("''a enclosed string''", '\'') = ['a enclosed string']
     *   StrUtil.removeEncloser("'a enclosed string", '\'')    = ['a enclosed string]
     *   StrUtil.removeEncloser(" 'a enclosed string' ", '\'') = [ 'a enclosed string' ]
     *   StrUtil.removeEncloser(""                     , '"')  = ""
     *   StrUtil.removeEncloser(null                   , '"')  = null
     *
     * </pre>
     * @param value
     * @param encloser
     * @return 前後のencloser文字が削除された文字列
     */
    public static String removeEncloser(String value, char encloser) {
        if (isEmpty(value)) {
            return value;
        }
        if (value.charAt(0) == encloser
                && value.charAt(value.length()-1) == encloser) {
            value = value.substring(1, value.length() - 1);
        }
        return value;
    }


    /**
     * <pre>
     * 文字配列中に、空の(String="" or null)オブジェクトがあれば、それを削除した配列を返す。
     *
     * Example:
     *   String[] array = {"000", "111", "", "333"};
     *   StrUtil.removeEmpty(array) = {"000", "111", "333"}
     *
     *   String[] array = {"", "111", "222", "333"};
     *   StrUtil.removeEmpty(array) = {"111", "222", "333"}
     *
     *   String[] array = {null, "111", null, "333", ""};
     *   StrUtil.removeEmpty(array) = {"111", "333"}
     *
     * </pre>
     *
     * @param values
     * @return 文字配列から空の情報を削除した配列
     */
    public static String[] removeEmpty(String[] values){
        int count = values.length - countEmpty(values);
        String[] returnArray = new String[count];
        int idx = 0;
        for(int i=0; i<values.length;i++){
            if( !isEmpty(values[i]) ){
                returnArray[idx] = values[i];
                idx++;
            }
        }
        return returnArray;
    }


    /**
     * <pre>
     * CSV文字列をString配列に分解し、空のカラムがあればそれを除いた結果を配列で返す。
     *
     * Example:
     *   String csv = "000,111,,,333,,,,444";
     *   StrUtil.removeEmpty(csv, ',') = {"000", "111", "333", "444"}
     *
     *   String csv = ",,,,,";
     *   StrUtil.removeEmpty(csv, ',') = {}
     *
     * </pre>
     * @param csvValue
     * @param separator
     * @return CSVを分解し、空カラムを削除した配列
     */
    public static String[] removeEmpty(String csvValue, char separator){
        String[] tmp = split(csvValue, separator);
        return removeEmpty(tmp);
    }


    /**
     * <pre>
     * 文字列配列中から、任意の文字に該当する要素を削除した配列を返す。
     *
     * Example:
     *   String[] array = { "000", "111", "222", "333", "000" };
     *   StrUtil.removeFromArray(array, "000", false) = {"111", "222", "333"}
     *   StrUtil.removeFromArray(array, "333", false) = {"000", "111", "222", "000"}
     *   StrUtil.removeFromArray(array, "ABC", false) = {"000", "111", "222", "333", "000"}
     *
     * </pre>
     * @param values 削除したい文字列要素を含む配列
     * @param keyword 削除したい文字列
     * @param ignoreCase 大文字小文字を問わないならtrue
     * @return 削除が完了した配列
     */
    public static String[] removeFromArray(String[] values, String keyword, boolean ignoreCase){
        return
        removeEmpty(
                replaceItem(values, keyword, null, ignoreCase)
        );
    }


    /**
     * <pre>
     * 文字列の全置換。
     *
     *   Example:
     *     StrUtil.replace("ABCD1234abcd", "ABCD", "@#$%&*", true)  = "@#$%&*1234@#$%&*"
     *     StrUtil.replace("ABCD1234abcd", "ABCD", "@#$%&*", false) = "@#$%&*1234abcd"
     *     StrUtil.replace("ABCD1234abcd", "ABCD", "///", true)     = "///1234///"
     *     StrUtil.replace("ABCD1234abcd", "ABCD", "///", false)    = "///1234abcd"
     *     StrUtil.replace("ABC123ABC", "ABC", "", false)           = "123"
     *     StrUtil.replace("ABC123ABC", "", "***", false)           = "ABC123ABC"
     *     StrUtil.replace("ABC123ABC", null, "", false)            = "ABC123ABC"
     *     StrUtil.replace("", "", "", false)                       = ""
     *     StrUtil.replace("", "", null, false)                     = ""
     *     StrUtil.replace("", null, "", false)                     = ""
     *     StrUtil.replace(null, "", "", false)                     = null
     *
     * </pre>
     * @param value 対象文字列
     * @param keyword 検索文字列
     * @param newWord 置換文字列
     * @param ignoreCase 検索時に大文字小文字を無視するならtrue、判別するならfalse
     * @return
     */
    public static String replace(String value, String keyword, String newWord, boolean ignoreCase){
        if(isEmpty(value) || isEmpty(keyword) || newWord == null){
            return value;
        }
        int valueLen = value.length();
        int searchWordLen = keyword.length();
        int begin = 0;
        int next = 0;
        StringBuilder rtn = new StringBuilder();

        if(ignoreCase){
            String lowerValue = value.toLowerCase();
            String lowerSearchWord = keyword.toLowerCase();
            while(true){
                next = lowerValue.indexOf(lowerSearchWord, begin);
                if(next < 0){
                    break;
                }
                String tmp = value.substring(begin, next);
                rtn.append(tmp);
                rtn.append(newWord);
                begin = next + searchWordLen;
            }
        }else{
            while(true){
                next = value.indexOf(keyword, begin);
                if(next < 0){
                    break;
                }
                String tmp = value.substring(begin, next);
                rtn.append(tmp);
                rtn.append(newWord);
                begin = next + searchWordLen;
            }
        }

        if(begin < valueLen){
            rtn.append(value.substring(begin));
        }
        return rtn.toString();
    }


    /**
     * <pre>
     * 文字列置換・最初に発見したもの１回のみ。
     *
     * Example:
     *   StrUtil.replaceOnce("ABCABC123ABC", "ABC", "123", true)  = "123ABC123ABC"
     *   StrUtil.replaceOnce("ABC123123ABC", "123", "***", true)  = "ABC***123ABC"
     *   StrUtil.replaceOnce("ABCABC123ABC", "123", "ABC", true)  = "ABCABCABCABC"
     *   StrUtil.replaceOnce("ABCABC123ABC", "abc", "123", false) = "ABCABC123ABC"
     *   StrUtil.replaceOnce("ABC123ABC", "", "123", false)       = "ABC123ABC"
     *   StrUtil.replaceOnce("ABC123ABC", "ACB", "", false)       = "ABC123ABC"
     *   StrUtil.replaceOnce("ABC123XYZ", "ABC", "", false)       = "123XYZ"
     *   StrUtil.replaceOnce("ABC123XYZ", null, "", false)        = "ABC123XYZ"
     *
     * </pre>
     * @param value 対象文字列
     * @param keyword 検索文字列
     * @param newWord 置換文字列
     * @param ignoreCase 検索時に大文字小文字を無視するならtrue、判別するならfalse
     * @return
     */
    public static String replaceOnce(String value, String keyword, String newWord, boolean ignoreCase){
        if(isEmpty(value) || isEmpty(keyword) || newWord == null){
            return value;
        }
        int searchWordLen = keyword.length();
        int begin = 0;
        int next = 0;

        StringBuilder rtn = new StringBuilder();

        if(ignoreCase){
            next = (value.toLowerCase()).indexOf(keyword.toLowerCase(), begin);
        }else{
            next = value.indexOf(keyword, begin);
        }
        if(next > -1){
            String tmp = value.substring(begin, next);
            rtn.append(tmp);
            rtn.append(newWord);
            begin = next + searchWordLen;
        }
        rtn.append(value.substring(begin));
        return rtn.toString();
    }


    /**
     * <pre>
     * 文字列配列中の任意の要素を置換する。
     * 要素内での置換判定は、完全一致とする。
     *
     * Example:
     *   String[] array = {"000", "ABC", "XYZ", "ABC"};
     *   StrUtil.replaceItem(array, "ABC", "STUVW", false) = {"000", "STUVW", "XYZ", "STUVW"}
     *   StrUtil.replaceItem(array, "XYZ", "56789", false) = {"000", "ABC", "56789", "ABC"}
     *   StrUtil.replaceItem(array, "ABC", ""     , false) = {"000", "", "XYZ", ""}
     *   StrUtil.replaceItem(array, "XYZ", null   , false) = {"000", "ABC", null, "ABC"}
     *   StrUtil.replaceItem(array, "AB", "XYZ"   , false) = {"000", "ABC", "XYZ", "ABC"}
     *
     * </pre>
     * @param values 置換したい文字列要素が含まれる文字列配列
     * @param keyword 検索文字列
     * @param newWord 置換文字列
     * @param ignoreCase 大文字小文字を問わないならtrue
     * @return 置換対象があれば、置換後の文字列配列
     */
    public static String[] replaceItem(String[] values, String keyword, String newWord, boolean ignoreCase){
        String[] result = new String[values.length];

        if(ignoreCase){
            for(int i=0; i<values.length; i++){
                if(keyword.toLowerCase().equals(values[i].toLowerCase())){
                    result[i] = newWord;
                }else{
                    result[i] = values[i];
                }
            }
        }else{
            for(int i=0; i<values.length; i++){
                if(keyword.equals(values[i])){
                    result[i] = newWord;
                }else{
                    result[i] = values[i];
                }
            }
        }
        return result;
    }


    /**
     * <pre>
     * 文字列中の連続した半角スペースとタブを、クォートの範囲を考慮してシュリンクする。
     *
     * Example:
     *   StrUtil.shrinkSpaces("ABC   123\t\t\tXYZ"      , 1, false) = "ABC 123 XYZ"
     *   StrUtil.shrinkSpaces("ABC   123\t\t\tXYZ"      , 2, false) = "ABC  123  XYZ"
     *   StrUtil.shrinkSpaces("ABC   123\t\t\tXYZ"      , 3, false) = "ABC   123   XYZ"
     *   StrUtil.shrinkSpaces("ABC   123\t\t\tXYZ"      , 4, false) = "ABC   123   XYZ"
     *   StrUtil.shrinkSpaces("ABC   123\t\t\tXYZ"      , 5, false) = "ABC   123   XYZ"
     *   StrUtil.shrinkSpaces("ABC   '1  2  3'\t\t\tXYZ", 1, false) = "ABC '1  2  3' XYZ"
     *   StrUtil.shrinkSpaces("ABC   '1  2  3'\t\t\tXYZ", 1, true ) = "ABC '1 2 3' XYZ"
     *   StrUtil.shrinkSpaces(" ABC  \"1 \t 2 \t 3\"\t\t\t\t\tXYZ   \t", 2, true ) = " ABC  \"1  2  3\"  XYZ  "
     *
     * </pre>
     * @param value シュリンクしたい文字が含まれる文字列
     * @param shrinkCount シュリンク後の文字を幾つ分にするか
     * @param enableQuoted ダブルクォートまたはシングルクォートで囲まれた範囲もシュリンク対象とするか
     * @return シュリンクされたvalue
     *
     */
    public static String shrinkSpaces(String value, int shrinkCount, boolean enableQuoted) {
        return shrink( value, new char[]{SPC, TAB}, " ", shrinkCount, enableQuoted );
    }


    /**
     * <pre>
     * 文字列中の連続する任意の文字を、クォートの範囲を考慮してシュリンクする。
     * クォート文字は、シングルクォートとダブルクォートの両方を認識する。
     *
     * 例：半角スペースやタブ文字が複数連続して存在している文字列中の
     * スペースやタブ文字を半角スペース４個にまとめたり、連続した複数の
     * 改行コードを１個にまとめるなど。
     *
     * ※：複数のchar型文字をremoveするメソッドとしても利用可能。
     *
     * Example:
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ', '\t'}, " ", 1, false) = "ABC 123 XYZ",
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ', '\t'}, " ", 2, false) = "ABC  123  XYZ",
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ', '\t'}, " ", 3, false) = "ABC   123   XYZ"
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ', '\t'}, " ", 4, false) = "ABC   123   XYZ"
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ', '\t'}, " ", 5, false) = "ABC   123   XYZ"
     *   StrUtil.shrink("ABC   '1  2  3'\t\t\tXYZ",         new char[]{' ', '\t'}, " ", 1, false) = "ABC '1  2  3' XYZ"
     *   StrUtil.shrink("ABC   '1  2  3'\t\t\tXYZ",         new char[]{' ', '\t'}, " ", 1, true)  = "ABC '1 2 3' XYZ"
     *   StrUtil.shrink("ABC   '1  2  3'\t\t\t\" X Y Z \"", new char[]{' ', '\t'}, " ", 1, true)  = [ABC '1 2 3' " X Y Z "]
     *   StrUtil.shrink("/#---- COMMENT ----#/", new char[]{'-'}, "*", 1, false)         = "/#* COMMENT *#/"
     *   StrUtil.shrink("/#---- COMMENT ----#/", new char[]{'-'}, "*", 0, false)         = "/# COMMENT #/"
     *   StrUtil.shrink("/#---- COMMENT ----#/", new char[]{'-'}, "" , 0, false)         = "/# COMMENT #/"
     *   StrUtil.shrink("\t\t /#-- COMMENT --#/ \t\t", new char[]{'\t'}, "" , 0, false)  = " /#-- COMMENT --#/ "
     *
     *   StrUtil.shrink("Line1\r\n\r\n\r\n\r\nLine2\r\n\r\nLine3\n", new char[]{'\r', '\n'}, "\n", 2, false) = "Line1\n\nLine2\n\nLine3\n"
     *   StrUtil.shrink("Line1\r\n\r\n\r\n\r\nLine2\r\n\r\nLine3\n", new char[]{'\r', '\n'}, "\n", 0, false) = "Line1Line2Line3"
     *   StrUtil.shrink("", new char[]{'A', 'B'}, "" , 1, false) = ""
     *   StrUtil.shrink(null, new char[]{'A', 'B'}, "" , 1, false) = null
     *
     *   StrUtil.shrink("AAA", null, "" , 1, false) = IllegalArgumentException
     *
     *
     * </pre>
     * @param value シュリンクしたい文字が含まれる文字列
     * @param shrinkTargets シュリンク対象となる文字をまとめた配列
     * @param shrinkedString シュリンク後の文字
     * @param shrinkCount シュリンク後の文字を幾つ分にするか
     * @param enableQuoted ダブルクォートまたはシングルクォートで囲まれた範囲もシュリンク対象とするか
     * @return シュリンクされたvalue
     */
    public static String shrink( String value, char[] shrinkTargets, String shrinkedString, int shrinkCount, boolean enableQuoted ) {
        if(isEmpty(value)){
            return value;
        }
        if(isEmpty(shrinkTargets)){
            throw new IllegalArgumentException("char[] shrinkTargets is empty.");
        }
        boolean isQuoted = false;
        StringBuilder rtn = new StringBuilder();
        int pos = 0;
        while(pos < value.length()){
            char c = value.charAt(pos);
            if(c == DQ || c== SQ){
                isQuoted = !isQuoted;
            }
            // found shrink target ? (sapces, tabs, etc...)
            if(contains(shrinkTargets, c)){
                if(isQuoted  && !enableQuoted){
                    // do not shrink
                    rtn.append(c);
                    pos++;
                }else{
                    // do shrink
                    for(int i=0; i<shrinkCount; i++){
                        // add shrinkedString by shrink-count
                        if( pos+i < value.length() && contains(shrinkTargets, c=value.charAt(pos+i))){
                            rtn.append(shrinkedString);
                        }else{
                            break;
                        }
                    }
                    // skip remaining space
                    while(contains(shrinkTargets, c=value.charAt(pos))){
                        pos++;
                        if(pos >= value.length()){
                            break;
                        }
                    }
                }
            }else{
                rtn.append(c);
                pos++;
            }
        }
        return rtn.toString();
    }


    /**
     * <pre>
     * 文字列中の連続する任意の文字を、クォートなどの囲み文字を指定してシュリンクする。
     *
     * Example:
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ','\t'}, "_", null)    = "ABC_123_XYZ"
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ','\t'}, "__", null)   = "ABC__123__XYZ"
     *   StrUtil.shrink("ABC   123\t\t\tXYZ", new char[]{' ','\t'}, "___", null)  = "ABC___123___XYZ"
     *   StrUtil.shrink("ABC 123\tXYZ", new char[]{' ','\t'}, "____", null)       = "ABC____123____XYZ"
     *
     *   StrUtil.shrink("ABC  \t  '1    2    3'\t \t \tXYZ", new char[]{' ','\t'}, "_", null)                  = "ABC_'1_2_3'_XYZ"
     *   StrUtil.shrink("ABC  '1  2  3'\t \t \tX  Y     Z",  new char[]{' ','\t'}, "_", new char[]{'\'','\''}) = "ABC_'1  2  3'_X_Y_Z"
     *   StrUtil.shrink("ABC  '1  2  3'\t \t \t\"X  Y  Z\"", new char[]{' ','\t'}, "_", new char[]{'"','"'})   = [ABC_'1_2_3'_"X  Y  Z"]
     *   StrUtil.shrink(" \t  '1  2  3'\t \t \tX  Y     Z",  new char[]{' ','\t'}, "_", new char[]{'\'','\''}) = "_'1  2  3'_X_Y_Z"
     *
     *   StrUtil.shrink("/#---- COMMENT ----#/", new char[]{'-'}, "*", null) = "/#* COMMENT *#/"
     *   StrUtil.shrink("/#---- COMMENT ----#/", new char[]{'-'}, "", null)  = "/# COMMENT #/"
     *
     *   StrUtil.shrink("ABC   (1 2 3)  XYZ", new char[]{' '}, "--", new char[]{'(',')'}) = "ABC--(1 2 3)--XYZ"
     *
     * </pre>
     * @param value シュリンクしたい文字が含まれる文字列
     * @param shrinkTargets シュリンク対象となる文字をまとめた配列
     * @param shrinkedString シュリンク後に置き換える固定文字列
     * @param encloser 囲み範囲を有効にしたい場合は、括弧やクォートなどの囲み文字列を２個指定。無指定なら囲み範囲認識無し。
     * @return シュリンクされたvalue
     *
     */
    public static String shrink( String value, char[] shrinkTargets, String shrinkedString, char[] encloser ) {
        if(isEmpty(value)){
            return value;
        }
        if(isEmpty(shrinkTargets)){
            throw new IllegalArgumentException("(char[])shrinkTargets is empty.");
        }
        boolean enabledEncloser = false;
        if(!isEmpty(encloser)){
            if(encloser.length != 2){
                throw new IllegalArgumentException("array size of (char[])encloser must be 2.");
            }else{
                enabledEncloser = true;
            }
        }
        StringBuilder rtn = new StringBuilder();
        int pos = 0;
        int valueLength = value.length();
        char c = 0;
        while(pos < valueLength){
            c = value.charAt(pos);
            // start encloser
            if(enabledEncloser){
                // enabled region of encloser
                if (c == encloser[0]) {
                    rtn.append(c);
                    if (pos >= valueLength - 1) {
                        return rtn.toString();
                    }
                    while ((c = value.charAt(++pos)) != encloser[1]) {
                        if (pos >= valueLength - 1) {
                            break;
                        }
                        rtn.append(c);
                    }

                }
            }
            // found shrink target ? (sapces, tabs, etc...)
            if(contains(shrinkTargets, c)){
                // do shrink
                while(contains(shrinkTargets, (c = value.charAt(pos)))){
                    pos++;
                    if(pos >= valueLength){
                        break;
                    }
                }
                rtn.append(shrinkedString);
            }else{
                rtn.append(c);
                pos++;
            }
        }
        return rtn.toString();
    }


    /**
     * <pre>
     * 引数１で指定された文字列を、引数２で指定された文字で分割する。
     * 分割後の各値は、前後の空白や改行コードがtrimされる。
     *
     * Example:
     *   StrUtil.split("  a,b , c,  d ", ',')          = {"a","b","c","d"}
     *   StrUtil.split("\"a\",\"b\",\"c\",\"d\"", ',') = {""a"",""b"",""c"",""d""}
     *   StrUtil.split("a,b,c,d", ':')                 = {"a,b,c,d"} (can not split.)
     *   StrUtil.split(null, ',')                      = java.lang.NullPointerException occurs.
     *   StrUtil.split("", ',')                        = null
     *
     * </pre>
     * @param value
     * @param splitter
     * @return 分解された文字配列
     */
    public static String[] split(String value, char splitter) {
        StringTokenizer toker = new StringTokenizer(value, Character.toString(splitter));
        String[] rtn = new String[toker.countTokens()];
        int index = 0;
        while (toker.hasMoreTokens()) {
            String token = trim(toker.nextToken());
            rtn[index++] = token;
        }
        return rtn;
    }


    /**
     * <pre>
     * 引数１で指定された文字列を、引数２で指定された文字で分割する。
     * その際、分割後の各カラムにブランク(半角スペース、タブ、改行)が含まれていたら、
     * 該当カラムのブランク以降は削除する。(カラム先頭の文字列のみがカラムとして認識される)
     * また、空のカラムはカラム自体が削除される。
     *
     * Example:
     *   StrUtil.splitIgnoreBlank("col1, col2, col3 nocolumn", ',')                  = "col1", "col2", "col3"
     *   StrUtil.splitIgnoreBlank("col1 not-col1, col2, col3 not-column", ',')       = "col1", "col2", "col3"
     *   StrUtil.splitIgnoreBlank("col1, col2 not-col2, col3 \r\n\tnot-column", ',') = "col1", "col2", "col3"
     *   StrUtil.splitIgnoreBlank("col1 not-column\r\n not-col1, col2 not-col2  , col3 \r\n\tnocolumn", ',')
     *                                                                               = "col1", "col2", "col3"
     *
     * </pre>
     *
     * @param value
     * @param separator
     * @return 引数１で指定された文字列を、引数２で指定された文字で分割した結果の文字配列
     */
    public static String[] splitIgnoreBlank(String value, char separator){
        if(isEmpty(value)){
            return null;
        }
        String[] tmp = split(value, separator);
        char[] blanks = {' ', '\t', '\r', '\n'};
        if(tmp.length > 1){
            // CSVカラム中に、ブランクを含んでいるものがあれば、ブランク以降は削除
            for(int i=0; i<tmp.length; i++ ){
                if(containsAnyBlank(tmp[i])){
                    tmp[i] = trim(tmp[i].substring(0, indexAny(blanks, tmp[i], false)));
                }
            }
        }
        // カラム自体が空のものを削除
        tmp = removeEmpty(tmp);
        return tmp;
    }


    /**
     * <pre>
     * 第１引数で与えられた文字列を、空白で分割してString型の配列で返す。
     * 空白は、半角スペース、タブ、改行(CR,LF)とする。
     * 第２引数で与えられるクォートで囲まれた範囲は、空白があっても分割しない。
     *
     * Example:
     *   String input = "col1  col2  'col3 col4'  col5\t\r\n 'col6 col7'";
     *   StrUtil.splitByAnyBlankToArray(input, '\'')
     *       = {"col1", "col2", "'col3 col4'", "col5", "'col6 col7'"}
     *
     *   input  = "'col1'''   col2 col3\ncol4  \t \r\n";
     *   StrUtil.splitByAnyBlankToArray(input, '\'')
     *       = { "'col1'''", "col2", "col3", "col4" }
     *
     *
     * </pre>
     * @param value
     * @param quote
     * @return 第１引数で与えられた文字列を空白で分割したString型の配列
     */
    public static String[] splitByAnyBlankToArray(String value, char quote){
        java.util.ArrayList<String> tokens = splitByAnyBlankToList(value, quote);
        String[] rtn = new String[tokens.size()];
        for(int i=0; i<rtn.length; i++){
            rtn[i] = tokens.get(i);
        }
        tokens.clear();
        tokens = null;
        return rtn;
    }


    /**
     * <pre>
     * 第１引数で与えられた文字列を、空白で分割してList型のオブジェクトで返す。
     * 空白は、半角スペース、タブ、改行(CR,LF)とする。
     * 第２引数で与えられるクォートで囲まれた範囲は、空白があっても分割しない。
     *
     * Example:
     *   String input = "col1  col2  'col3 col4'  col5\t\r\n 'col6 col7'";
     *   List result = new ArrayList();
     *   result = StrUtil.splitByAnyBlankToList(input, '\'');
     *   result.get(0) = "col1"
     *   result.get(1) = "col2"
     *   result.get(2) = "'col3 col4'"
     *   result.get(3) = "col5"
     *   result.get(4) = "'col6 col7'"
     *
     * </pre>
     * @param value
     * @param quote
     * @return 第１引数で与えられた文字列を空白で分割したList型のオブジェクト
     */
    public static java.util.ArrayList<String> splitByAnyBlankToList(String value, char quote){
        char[] valueArray = value.toCharArray();
        java.util.ArrayList<String> tokens = new java.util.ArrayList<String>();

        boolean isInQuote = false;
        int startPos = indexOfNextChar(value, 0);
        int i=startPos;
        while(i < valueArray.length){
            char c = valueArray[i];
            if(c == quote){
                // found quote
                if(i == 0  ||  valueArray[i-1] != quote){
                    // クオートが１文字目または、２文字目以降かつ前の文字はクオート以外
                    isInQuote = !isInQuote;
                }
            }
            else if(isBlankAny(c) && !isInQuote){
                String token = value.substring(startPos, i);
                startPos = indexOfNextChar(value, i);
                if( startPos > -1 ){
                    i = startPos;
                    tokens.add(token);
                    continue;
                }else{
                    tokens.add(token);
                    break;
                }
            }
            i++;
        }
        if(startPos > -1){
            tokens.add(value.substring(startPos));
        }
        return tokens;
    }


    /**
     * <pre>
     * WildCardとそれ以外の文字列とを分割する。
     * ワイルドカード以外にも、任意のクォート、括弧、例外文字を認識して分割する。
     * 　デフォルトのクォート：["], [']
     * 　デフォルトの括弧　　："(","[","<","{","}",">","]",")"
     * 　デフォルトの例外文字："=", "|"
     * これらは、任意に再指定できる。
     *
     * Split String-type-value using wildcard-characters.
     * with recognize three options: some quotes, some brackets and some excepted-chars.
     *
     *
     * WildCard expression:
     *   "_" = a char
     *   "$" = a space or tab
     *   "~" = a CR or LF
     *   "&" = a space, tab, CR and LF
     *   "#" = a '0'-'9'
     *   "@" = a 'A'-'Z', 'a'-'z'
     *   "?" = a number or alphabet or '-' or '_'
     *   "`" = a URL string
     *
     *   "%"  = any chars (space/tab/CR/LF is containing)
     *   "$+" = spaces or tabs
     *   "~+" = CRs or LFs
     *   "&+" = spaces, tabs, CRs and LFs
     *   "#+" = region of numbers
     *   "@+" = region of alphabets
     *   "?+" = numbers or alphabets or '-' or '_'
     *   "`+" = URL strings
     *     ex. URL-string is not containing '"', '\'', '\r', '\n', '\t', ' ', '<', '>', '(', ')'.
     *
     *   "{{" - "}}" = region of 'COMMAND'
     *     ex. "{{*" - "}}" = command 'OR'
     *         "{{!" - "}}" = command 'NOT and OR'
     *
     *         "{{*http://|https://}}" = "http://" OR "https://"
     *         "{{!http://|https://}}" = NOT("http://" AND "https://")
     *
     * Escaped char expression:
     *   "\_"    = "_"  as String-Data
     *   "\$"    = "$"  as String-Data
     *   "\~"    = "~"  as String-Data
     *   "\&"    = "&"  as String-Data
     *   "\#"    = "#"  as String-Data
     *   "\%"    = "%"  as String-Data
     *   "\?"    = "?"  as String-Data
     *   "\`"    = "`"  as String-Data
     *   "\$+"   = "$+" as String-Data
     *   "\!+"   = "!+" as String-Data
     *   "\&+"   = "&+" as String-Data
     *   "\#+"   = "#+" as String-Data
     *   "\@+"   = "@+" as String-Data
     *   "\?+"   = "?+" as String-Data
     *   "\`+"   = "`+" as String-Data
     *
     * Option char expression:
     * 　Default quote list         : ["], [']
     * 　Default bracket list       : "(", "[", "<", "{", "}", ">", "]", ")"
     * 　Default excepted-char list : "=", "|"
     *
     *   and see also: splitByWildCard(String value, char[] quotes, char[] brackets, char[] exceptChars)
     *
     * Example:
     *   StrUtil.splitByWildCard("_")                     = {"_"}
     *   StrUtil.splitByWildCard("_%")                    = {"_", "%"}
     *   StrUtil.splitByWildCard("_%~&$")                 = {"_", "%", "~", "&", "$"}
     *   StrUtil.splitByWildCard("_%~&$#@?`")             = {"_", "%", "~", "&", "$", "#", "@", "?", "`"}
     *   StrUtil.splitByWildCard("\\_\\%")                = {"\_\%"}
     *   StrUtil.splitByWildCard("\\_%")                  = {"\_", "%"}
     *   StrUtil.splitByWildCard("_\\%")                  = {"_", "\%"}
     *   StrUtil.splitByWildCard("\\_\\_")                = {"\_\_"}
     *   StrUtil.splitByWildCard("\\__\\__")              = {"\_", "_", "\_", "_"}
     *   StrUtil.splitByWildCard("\\_%~&$\\#")            = {"\_", "%", "~", "&", "$", "\#"}
     *   StrUtil.splitByWildCard("_\\%~\\&$\\#")          = {"_", "\%", "~", "\&", "$", "\#"}
     *   StrUtil.splitByWildCard("_\\%\\~\\&\\$#")        = {"_", "\%\~\&\$", "#"}
     *   StrUtil.splitByWildCard("\\_\\%\\~\\&\\$\\#")    = {"\_\%\~\&\$\#"}
     *   StrUtil.splitByWildCard("$+")                    = {"$+"}
     *   StrUtil.splitByWildCard("$+~+")                  = {"$+", "~+"}
     *   StrUtil.splitByWildCard("$+~+&+")                = {"$+", "~+", "&+"}
     *   StrUtil.splitByWildCard("$+~+&+#+@+?+`+")        = {"$+", "~+", "&+", "#+", "@+", "?+", "`+"}
     *   StrUtil.splitByWildCard("\\$+")                  = {"\$+"}
     *   StrUtil.splitByWildCard("\\$+\\\\")              = {"\$+", "\\"}
     *   StrUtil.splitByWildCard("\\$+$\\__")             = {"\$+", "$", "\_", "_"}
     *   StrUtil.splitByWildCard("\\$+~+&+\\#+")          = {"\$+", "~+", "&+", "\#+"}
     *   StrUtil.splitByWildCard("$+\\~+\\&+#+")          = {"$+", "\~+\&+", "#+"}
     *   StrUtil.splitByWildCard("\\$+\\~+&+\\#+")        = {"\$+\~+", "&+", "\#+"}
     *   StrUtil.splitByWildCard("\\$+\\~+&+@+\\@\\@+")   = {"\$+\~+", "&+", "@+", "\@\@+"}
     *   StrUtil.splitByWildCard("\\$+\\~+\\&+\\#+")      = {"\$+\~+\&+\#+"}
     *   StrUtil.splitByWildCard("$+\\~+\\_%\\&+~\\$##+") = {"$+", "\~+\_", "%", "\&+", "~", "\$", "#", "#+"}
     *   StrUtil.splitByWildCard("<TAG\\_NAME&+attr1&+=&+\"#+\" />")
     *                         = {"<", "TAG\_NAME", "&+", "attr1", "&+", "=", "&+", "\"", "#+", "\"", " /", ">"}
     *   StrUtil.splitByWildCard("select&+*&+from&+A\\_TABLE&+where&+USER\\_ID&+LIKE&+'uid\\_%'")
     *                         = {"select", "&+", "*", "&+", "from", "&+", "A\_TABLE", "&+"
     *                         , "where", "&+", "USER\\_ID", "&+", "LIKE", "&+", "'", "uid\_", "%", "'"}
     *
     *   StrUtil.splitByWildCard("\\")                    = IllegalArgumentException
     *   StrUtil.splitByWildCard("\\\\")                  = {"\"}
     *   StrUtil.splitByWildCard("\\\\\\")                = IllegalArgumentException
     *   StrUtil.splitByWildCard("\\\\\\\\")              = {"\", "\"}
     *   StrUtil.splitByWildCard("ABCD\\\\EFGH")          = {"ABCD", "\\", "EFGH"}
     *
     *   StrUtil.splitByWildCard("{{*http://|https://")   = {"{{", "*", "http://", "|", "https://"}
     *   StrUtil.splitByWildCard("{{!ftp://|gopher://")   = {"{{", "!", "ftp://",  "|", "gopher://"}
     *
     * </pre>
     * @param value ワイルドカードを区切りとして分割したい文字列
     * @return 分割された文字列
     *
     */
    public static String[] splitByWildCard(String value) {
        return splitByWildCard(value, null, null, null);
    }


    /**
     * <pre>
     * WildCardとそれ以外の文字列とを分割する。
     * 任意のオプション文字配列を指定して処理をおこなう。
     * Split a String-value using wildcard-characters.
     * This method can use specifying customized quote-list, bracket-list and excepted-character-list.
     *
     * Example:
     *   char[] notSpecifiedAnyChars = {};
     *   char[] customQuotes         = {'"'      };
     *   char[] customBrackets       = {'<', '>' };
     *   char[] customExceptedChars  = {'='      };
     *
     *   StrUtil.splitByWildCard("Enabled \"Double quotes\" and 'Single quotes'.", null, null, null)
     *                         = {"Enabled ", "\"", "Double quotes", "\"", " and ", "'", "Single quotes", "'", "."}
     *   StrUtil.splitByWildCard("Disabled \"Double quotes\" and 'Single quotes'.", notSpecifiedAnyChars, null, null)
     *                         = {"Disabled \"Double quotes\" and 'Single quotes'."}
     *   StrUtil.splitByWildCard("Custome quotes \"Double quotes\" 'ONLY'.",customQuotes , null, null)
     *                         = {"Custome quotes ", "\"", "Double quotes", "\"", " 'ONLY'."}
     *
     *   StrUtil.splitByWildCard("Enabled <Brackets>(brackets)[BrAcKeTs]{bRaCkEtS}.", null, null, null)
     *                         = {"Enabled ", "<", "Brackets", ">", "(", "brackets", ")", "[", "BrAcKeTs", "]", "{", "bRaCkEtS", "}", "."}
     *   StrUtil.splitByWildCard("Disabled <Brackets>(brackets)[BrAcKeTs]{bRaCkEtS}.", null, notSpecifiedAnyChars, null)
     *                         = {"Disabled <Brackets>(brackets)[BrAcKeTs]{bRaCkEtS}."}
     *   StrUtil.splitByWildCard("Custom brackets <Brackets>(brackets)[BrAcKeTs]{bRaCkEtS}.", null, customBrackets, null)
     *                         = {"Custom brackets ", "<", "Brackets", ">", "(brackets)[BrAcKeTs]{bRaCkEtS}."}
     *
     *   StrUtil.splitByWildCard("Enabled Excepted Characters |BAR|=EQUAL=", null, null, null)
     *                         = {"Enabled Excepted Characters ", "|", "BAR", "|", "=", "EQUAL", "="}
     *   StrUtil.splitByWildCard("Disabled Excepted Characters |BAR|=EQUAL=", null, null, notSpecifiedAnyChars)
     *                         = {"Disabled Excepted Characters |BAR|=EQUAL="}
     *   StrUtil.splitByWildCard("Custom Excepted Characters |BAR|=EQUAL=", null, null, customExceptedChars)
     *                         = {"Custom Excepted Characters |BAR|", "=", "EQUAL", "="}
     *
     *
     * </pre>
     * @param value ワイルドカードを区切りとして分割したい文字列
     * @param quotes 任意のクォートとして扱いたい文字配列(デフォルトを使うならnull)
     * @param brackets 任意の括弧として扱いたい文字配列(デフォルトを使うならnull)
     * @param exceptChars 任意の例外文字として扱いたい文字配列(デフォルトを使うならnull)
     * @return 分割された文字列配列
     * @see splitByWildCard(String value)
     */
    public static String[] splitByWildCard(String value, char[] quotes, char[] brackets, char[] exceptChars)  {
        if(isEmpty(value)){
            return null;
        }
        if(quotes == null){
            quotes = DEFAULT_QUOTES;
        }
        if(brackets == null){
            brackets = DEFAULT_BRACKETS;
        }
        if(exceptChars == null){
            exceptChars = DEFAULT_EXCEPTED_CHARS;
        }
        int searchIndex = 0;
        int valueLen = value.length();
        java.util.List<String> tokens = new java.util.ArrayList<String>();
        StringBuilder token = new StringBuilder();

        while(searchIndex < valueLen){
            char c = value.charAt(searchIndex);

            // データ末尾-2以前に"\"を発見したら
            if( (c == '\\') && (searchIndex < valueLen - 2)){
                // 2文字WildCardかどうか？
                if(contains(WILDCARDS_LONG, value, searchIndex+1, 2, false)){
                    // "\"に続くWildCardなら、通常文字列。
                    for(int i=0; i<3; i++){
                        token.append(value.charAt(searchIndex + i));
                    }
                    searchIndex += 3;
                    continue;
                }
                char c2 = value.charAt(searchIndex + 1);
                // 2文字目も"\"だったら
                if(c2 == '\\'){
                    // "\"そのものを示すtokenとしてtokenリストに追加
                    if(token.length() > 0){
                        tokens.add(token.toString());
                        token.delete(0, token.length());
                    }
                    token.append(c);
                    searchIndex += 2;
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                    continue;
                }
            }

            // データ末尾-1以前に"\"を発見したら
            if( (c == '\\') && (searchIndex < valueLen - 1)){
                // 1文字WildCardかどうか？
                char c2 = value.charAt(searchIndex + 1);
                if(contains(WILDCARDS_SHORT, c2)){
                    // "\"に続くWildCardなら、通常文字列。
                    token.append(c);
                    token.append(c2);
                    searchIndex += 2;
                    continue;
                }
                // 続く2文字目も"\"だったら
                if(c2 == '\\'){
                    // "\"をエスケープとしてtokenリストに追加
                    if(token.length() > 0){
                        tokens.add(token.toString());
                        token.delete(0, token.length());
                    }
                    token.append(c);
                    searchIndex += 2;
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                    continue;
                }
                else{
                    // "\"が単独で存在した場合はエラー
                    throw new IllegalArgumentException(
                            "\n\tBackslash:'\\' can not be alone.\n\tindex="
                            +searchIndex
                            +" in \""
                            +value+"\"");
                }
            }

            // データ末尾に"\"を発見
            if( (searchIndex == valueLen - 1) && (c == '\\') ){
                // "\"が単独で存在した場合はエラー
                throw new IllegalArgumentException(
                        "\n\tBackslash:'\\' can not be alone at last index.\n\tindex="
                        +searchIndex
                        +" in \""
                        +value+"\"");
            }



            c = value.charAt(searchIndex);
            // 2文字WildCardを見つけたら
            if(contains(WILDCARDS_LONG, value, searchIndex, 2, false)){
                if(token.length() > 0){
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                }
                // WildCardはコマンド開始指示 "{{" だった
                if(value.startsWith(WILDCARDS_LONG[COMMAND_OPEN], searchIndex)){
                    String commandOpen = value.substring(searchIndex, searchIndex + 2);
                    tokens.add(commandOpen);
                    searchIndex+=2;
                    char cmd = value.charAt(searchIndex);
                    if(contains(COMMANDS, cmd)){
                        // サブコマンド "*", "!" を分離
                        tokens.add(Character.toString(cmd));
                        searchIndex++;
                    }
                    continue;
                }
                // tokenとしてtokenリストに追加
                token.append(c);
                token.append(value.charAt(searchIndex+1));
                searchIndex += 2;
                tokens.add(token.toString());
                token.delete(0, token.length());
                continue;
            }

            c = value.charAt(searchIndex);
            // 1文字WildCardを見つけたら
            if(contains(WILDCARDS_SHORT, c)){
                if(token.length() > 0){
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                }
                // tokenとしてtokenリストに追加
                token.append(c);
                searchIndex++;
                tokens.add(token.toString());
                token.delete(0, token.length());
                continue;
            }
            // 任意のクォートリスト使用
            if(contains(quotes, c)){
                if(token.length() > 0){
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                }
                token.append(c);
                searchIndex++;
                tokens.add(token.toString());
                token.delete(0, token.length());
                continue;
            }
            // 任意の括弧リスト使用
            if(contains(brackets, c)){
                if(token.length() > 0){
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                }
                token.append(c);
                searchIndex++;
                tokens.add(token.toString());
                token.delete(0, token.length());
                continue;
            }
            // 任意の例外文字リスト使用
            if(contains(exceptChars, c)){
                if(token.length() > 0){
                    tokens.add(token.toString());
                    token.delete(0, token.length());
                }
                token.append(c);
                searchIndex++;
                tokens.add(token.toString());
                token.delete(0, token.length());
                continue;
            }
            // その他は通常文字
            c = value.charAt(searchIndex);
            token.append(c);
            searchIndex++;
        }
        if(token.length() > 0){
            tokens.add(token.toString());
        }
        //printArray(tokens.toArray(), false);
        return toArray(tokens);
    }


    /**
     * <pre>
     * long又はint型で与えられた整数値を、３桁カンマ区切りの文字列に変換する。
     *
     * Example:
     *    FormatUtils.getCommaSeparatedNumber(999)      = "999"
     *    FormatUtils.getCommaSeparatedNumber(1000)     = "1,000"
     *    FormatUtils.getCommaSeparatedNumber(1234567)  = "1,234,567"
     *    FormatUtils.getCommaSeparatedNumber(Integer.MAX_VALUE)           = "2,147,483,647"
     *    FormatUtils.getCommaSeparatedNumber(Integer.MIN_VALUE)           = "-2,147,483,648"
     *    FormatUtils.getCommaSeparatedNumber((long)Integer.MAX_VALUE + 1) = "2,147,483,648"
     *    FormatUtils.getCommaSeparatedNumber((long)Integer.MIN_VALUE - 1) = "-2,147,483,649"
     *
     * </pre>
     *
     * @param value
     * @return
     */
    public static String getCommaSeparatedNumber(long value){
        NumberFormat formatter = NumberFormat.getNumberInstance();
        String rtn = formatter.format(value);
        formatter = null;
        return rtn;
    }



    /**
     * <pre>
     * 文字列の最初から、指定の文字がヒットする直前までの間の文字列を返す。
     * 文字列のサーチに際して、大文字小文字は区別しない。
     * 返す文字列の前後に空白がある場合、空白もそのまま含めて返す。
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     *
     * Example:
     *   StrUtil.getBetweenStartAnd("from", "* from TABLE", true) = "* "
     *   StrUtil.getBetweenStartAnd("FROM", "* TABLE", true)      = null
     *   StrUtil.getBetweenStartAnd("from", "col1, col2, col3 \t FROM table", true) = "col1, col2, col3 \t "
     *   StrUtil.getBetweenStartAnd("from", "* from TABLE", false) = "* "
     *   StrUtil.getBetweenStartAnd("FROM", "* TABLE", false)      = null
     *   StrUtil.getBetweenStartAnd("from", "col1, col2, col3 \t FROM table", false) = null
     *   StrUtil.getBetweenStartAnd("", "", true)      = null
     *   StrUtil.getBetweenStartAnd("", null, true)    = null
     *   StrUtil.getBetweenStartAnd(null, "", true)    = null
     *   StrUtil.getBetweenStartAnd(null, null, true)  = null
     *   StrUtil.getBetweenStartAnd("", "", false)     = null
     *   StrUtil.getBetweenStartAnd("", null, false)   = null
     *   StrUtil.getBetweenStartAnd(null, "", false)   = null
     *   StrUtil.getBetweenStartAnd(null, null, false) = null
     *
     * </pre>
     *
     * @param keyword
     * @param value
     * @param ignoreCase
     * @return 文字列の最初から、指定の文字がヒットする直前までの間の文字列
     */
    public static String getBetweenStartAnd(String keyword, String value, boolean ignoreCase){
        String rtn = null;
        if(isEmpty(value)
                || isEmpty(keyword)){
            return rtn;
        }
        String chkValue = value;
        String chkToValue = keyword;
        if(ignoreCase){
            chkValue = chkValue.toLowerCase();
            chkToValue = chkToValue.toLowerCase();
        }
        int indexTo = chkValue.indexOf(chkToValue);
        if(indexTo > -1){
            rtn = value.substring(0, indexTo);
        }
        return rtn;
    }

    /**
     * <pre>
     * 対象文字列の最初から、指定の検索文字配列の中の文字列がヒットした場合に、
     * 一番最初にヒットした配列中文字の直前までの間の文字列を返す。
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     * １つもヒットが無ければ、nullを返す。
     *
     * Example:
     *   String[] list = {"WHERE","GROUP BY","HAVING","ORDER BY","LIMIT"};
     *   StrUtil.getBetweenStartAndFirstOf(list, " select * from table where id='1'", true)
     *      = " select * from table "
     *   StrUtil.getBetweenStartAndFirstOf(list, "select * from table1 UNSUPPORTED CLAUSE", true) = null
     *   StrUtil.getBetweenStartAndFirstOf(list, "Where are you from ?", true) = ""
     *   StrUtil.getBetweenStartAndFirstOf(list, "Where are you from ?", false) = null
     *   StrUtil.getBetweenStartAndFirstOf(list, "Medical Institutes where foreign languages are available.", true)
     *      = "Medical Institutes "
     *   StrUtil.getBetweenStartAndFirstOf(list, "Medical Institutes where foreign languages are available.", false) = null
     *
     * </pre>
     *
     * @param keywords
     * @param value
     * @param ignoreCase
     * @return 一番最初にヒットした配列中文字の直前までの間の文字列
     */
    public static String getBetweenStartAndFirstOf(String[] keywords, String value, boolean ignoreCase){
        String rtn = null;
        int index = indexAny(keywords, value, ignoreCase);
        if(index > -1){
           rtn = value.substring(0, index);
        }
        return rtn;
    }

    /**
     * <pre>
     * 指定文字列中から、最初のスタート文字、エンド文字で囲まれた範囲を返す。
     * 括弧内の文字列取得など。
     *
     * Example:
     *   StrUtil.getRangeOf('(', ')', "(ABCD)")           = "ABCD"
     *   StrUtil.getRangeOf('(', ')', "(ABCD) (123,456)") = "ABCD"
     *   StrUtil.getRangeOf('<', '>', " 123<ABCD>123 ")   = "ABCD"
     *   StrUtil.getRangeOf('{', '}', "\t {A B C D} \n")  = "A B C D"
     *   StrUtil.getRangeOf('[', ']', "[*]")              = "*"
     *   StrUtil.getRangeOf('(', ')', "(")                = null
     *   StrUtil.getRangeOf('(', ')', ")")                = null
     *   StrUtil.getRangeOf('(', ')', "")                 = null
     *   StrUtil.getRangeOf('(', ')', null)               = null
     *
     * </pre>
     * @param start
     * @param end
     * @param value
     * @return 最初のスタート文字、エンド文字で囲まれた範囲
     */
    public static String getRangeOf(char start, char end, String value){
        if(isEmpty(value)){
            return null;
        }
        char[] values = value.toCharArray();
        int length = values.length;
        int startIndex = 0;
        // start位置を探す
        while(startIndex < length){
            if(values[startIndex] == start){
                break;
            }
            startIndex++;
        }
        // start位置、発見できず
        if(startIndex == length){
            return null;
        }
        // end位置を探す
        int endIndex = 0;
        while(endIndex < length){
            if(values[endIndex] == end){
                break;
            }
            endIndex++;
        }
        // end位置、発見できず
        if(endIndex == length){
            return null;
        }

        // 範囲あり
        startIndex++;
        endIndex--;
        // start、endの間に文字が無ければエラー
        if(startIndex > endIndex){
            return null;
        }

        StringBuilder sb = new StringBuilder();
        while(startIndex <= endIndex){
            sb.append(values[startIndex++]);
        }
        return sb.toString();
    }


    /**
     * <pre>
     * 文字列の最初から指定の文字をサーチし、ヒットした直後の文字列を、その次の空白直前まで返す。
     * （文字列中に含まれる指定の単語から、空白を挟んだ直後の単語を返す）
     * 大文字小文字を問わずマッチングしたい場合は、第３引数をtrueにする。
     *
     * Example:
     *   StrUtil.getWordAfterOf("from", "select * from TABLE", true)  = "TABLE"
     *   StrUtil.getWordAfterOf("from", "select * from TABLE ", true) = "TABLE"
     *   StrUtil.getWordAfterOf("from", "select * from TABLE;", true) = "TABLE;"
     *   StrUtil.getWordAfterOf("from", "  select col1, col2, col3  from TABLE\t", true) = "TABLE"
     *   StrUtil.getWordAfterOf("from", "  select col1, col2, col3  from TABLE; ", true) = "TABLE;"
     *   StrUtil.getWordAfterOf("from", "  select col1, col2, col3  from TABLE where col5='AAA';  ", true) = "TABLE"
     *   StrUtil.getWordAfterOf("from", "select * from TABLE;", false) = "TABLE;"
     *   StrUtil.getWordAfterOf("from", "select * from TABLE ", false) = "TABLE"
     *   StrUtil.getWordAfterOf("FROM", "select * from TABLE ", false) = null
     *   StrUtil.getWordAfterOf("", "", true)     = null
     *   StrUtil.getWordAfterOf("", null, true)   = null
     *   StrUtil.getWordAfterOf(null, "", true)   = null
     *   StrUtil.getWordAfterOf(null, null, true) = null
     *   StrUtil.getWordAfterOf("", "", false)     = null
     *   StrUtil.getWordAfterOf("", null, false)   = null
     *   StrUtil.getWordAfterOf(null, "", false)   = null
     *   StrUtil.getWordAfterOf(null, null, false) = null
     *
     * </pre>
     *
     * @param keyword
     * @param value
     * @param ignoreCase
     * @return ヒットした直後の文字列を、その次の空白直前までの文字列
     */
    public static String getWordAfterOf(String keyword, String value, boolean ignoreCase){
        String rtn = null;
        if(isEmpty(keyword) || isEmpty(value)){
            return rtn;
        }
        // if [ignoreCase = true]
        //    "from TABLE where col5 = 'ABC'" , "from" -> "TABLE"
        //    "from TABLE"                    , "FROM "-> "TABLE"
        // if [ignoreCase = false]
        //    "from TABLE where col5 = 'ABC'" , "from" -> "TABLE"
        //    "from TABLE"                    , "FROM "-> null
        String chkValue = value;
        String chkFindValue = keyword;
        if(ignoreCase){
            chkValue = value.toLowerCase();
            chkFindValue = keyword.toLowerCase();
        }
        int foundPos = chkValue.indexOf(chkFindValue);
        if(foundPos > -1){
            int blankPosAfterFoundPos = indexOfNextBlank(value, foundPos);
            int nextBlank = indexOfNextBlank(value, blankPosAfterFoundPos+1);
            if(nextBlank < 0){
                nextBlank = value.length();
            }
            rtn = value.substring(blankPosAfterFoundPos, nextBlank);
        }
        return trim(rtn);
    }


    /**
     * <pre>
     * 与えられた文字列中、指定indexから次の空白までの１語を返す。
     *
     * Example:
     *   StrUtil.getWordFrom(0,  "word1  word2   word3\tword4\r\nword5") = "word1"
     *   StrUtil.getWordFrom(1,  "word1  word2   word3\tword4\r\nword5") = "ord1"
     *   StrUtil.getWordFrom(3,  "word1  word2   word3\tword4\r\nword5") = "d1"
     *   StrUtil.getWordFrom(5,  "word1  word2   word3\tword4\r\nword5") = ""
     *   StrUtil.getWordFrom(7,  "word1  word2   word3\tword4\r\nword5") = "word2"
     *   StrUtil.getWordFrom(15, "word1  word2   word3\tword4\r\nword5") = "word3"
     *   StrUtil.getWordFrom(20, "word1  word2   word3\tword4\r\nword5") = ""
     *   StrUtil.getWordFrom(26, "word1  word2   word3\tword4\r\nword5") = ""
     *   StrUtil.getWordFrom(27, "word1  word2   word3\tword4\r\nword5") = ""
     *   StrUtil.getWordFrom(28, "word1  word2   word3\tword4\r\nword5") = "word5"
     *   StrUtil.getWordFrom(0,"AAA" ) = "AAA"
     *   StrUtil.getWordFrom(0,"AAA ") = "AAA"
     *   StrUtil.getWordFrom(1,"AAA ") = "AA"
     * </pre>
     *
     * @param index
     * @param value
     * @return 指定indexから次の空白までの１語
     */
    public static String getWordFrom(int index, String value){
        int nextBlank = indexOfNextBlank(value, index);
        if(nextBlank == value.length() - 1){
            nextBlank++;
        }else if(nextBlank < 0){
            nextBlank = value.length();
        }
        String rtn = value.substring(index, nextBlank);
        return trim(rtn);
    }


    /**
     * <pre>
     * カンマで区切られた複数の値が連続するvalueを分解し、String配列で返す。
     * 分解された値は、前後の空白はtrimされる。
     *
     * Example:
     *   StrUtil.getStringArray("  value-1,  value-2,  value-3  ")
     *          = {"value-1", "value-2", "value-3"}
     *
     * </pre>
     * @param value カンマ区切りの文字列
     * @return 引数valueが分解された配列
     */
    public static String[] getStringArray(String value){
        return split(value, ',');
    }

    /**
     * <pre>
     * 任意の文字で区切られた複数の値が連続するvalueを分解し、String配列で返す。
     *
     * Example:
     *   StrUtil.getStringArray("  'value-1' : ' value-2 ' : '  value-3  '  ", ':')
     *          = {"'value-1'", "' value-2 '", "'  value-3  '"}
     *
     * </pre>
     * @param value 任意の文字で区切られた文字列
     * @param separator 区切り文字
     * @return 引数valueが分解された配列
     */
    public static String[] getStringArray(String value, char separator){
        return split(value, separator);
    }



    /**
     * <pre>
     * 行末に\を付加して複数行にまたがって１行分の情報が記述されているような情報から、
     * 改行コードのみを削除して１行分の文字列として返す。
     *
     * +---------------------------
     * |font = family1 family2 \
     * | - size1 size2 \
     * | - bold italic
     * |
     *
     * というデータが、
     * "font = family1 family2 \\\r\n - size1 size2 \\\r\n - bold italic   "
     * と表現される場合、結果は
     * "font = family1 family2  - size1 size2  - bold italic   "
     * が返される。
     *
     * Example:
     *   StrUtil.getLineFromMultiLine("key=value1,\\\r\nvalue2,\\\r\nvalue3\r\n\r\n,value4")
     *     = "key=value1,value2,value3,value4"
     *   StrUtil.getLineFromMultiLine("key=value1,\\\r\n   value2,\\\r\n  value3\r\n\r\n,  value4")
     *     = "key=value1,   value2,  value3,  value4"
     *   StrUtil.getLineFromMultiLine("key=value1-1\\\nvalue1-2\t  ")
     *     = "key=value1-1value1-2\t  "
     *   StrUtil.getLineFromMultiLine("font = family1 family2 \\\r\n - size1 size2 \\\r\n - bold italic  ")
     *     = "font = family1 family2  - size1 size2  - bold italic  "
     *
     *   StrUtil.getLineFromMultiLine("")   = ""
     *   StrUtil.getLineFromMultiLine(null) = null
     *
     * </pre>
     * @param value 対象となる複数行分の文字列
     * @return １行にまとめられた文字列
     */
    public static String getLineFromMultiLine(String value){
        if(isEmpty(value)){
            return value;
        }
        int endPos = value.length();
        int pos = 0;
        StringBuilder rtn = new StringBuilder();
        char[] crlf = {CR, LF};
        char c = 0;
        char cn = 0;
        while(pos < endPos){
            c = value.charAt(pos);
            if(pos < endPos - 2){
                cn = value.charAt(pos + 1);
            }else{
                cn = 0;
            }
            if( contains(crlf, cn) && c == '\\'){
                pos++;
                while( contains(crlf,(c = value.charAt(pos))) ){
                    pos++;
                }
                continue;
            }
            if( contains(crlf, c) ){
                pos++;
                continue;
            }
            rtn.append(c);
            pos++;
        }
        return rtn.toString();
    }


    /**
     * <pre>
     * ランダムな16進数文字列を指定桁数で生成する。
     * 生成処理のコアは、
     *   new Random(System.nanoTime()).nextLong() XOR System.nanoTime()
     * となる。
     *
     * Example:
     *   StrUtil.getRandomHexString( 8, true)  = "649ACB4B"
     *   StrUtil.getRandomHexString(16, true)  = "16EC543438EB1F98"
     *   StrUtil.getRandomHexString( 4, false) = "a12dbe49be24"
     *   StrUtil.getRandomHexString(12, false) = "3a2c"
     *   StrUtil.getRandomHexString( 0, false) = ""
     *
     * </pre>
     * @param length 生成桁数
     * @param enableUpperCase 16進数中のa-fを大文字で返す場合はtrue、それ以外はfalse。
     * @return
     */
    public static String getRandomHexString(int length, boolean enableUpperCase){
        if(length < 1) return null;
        StringBuilder seed = new StringBuilder();
        while(seed.length() < length / 16 + length){
            seed.append(Long.toHexString(new Random(System.nanoTime()).nextLong() ^ System.nanoTime()));
        }
        String rtn = seed.toString().substring(0, length);
        if(enableUpperCase){
            rtn = rtn.toUpperCase();
        }
        return rtn;
    }


    /**
     * <pre>
     * java.lang.String#substring(int arg0, int arg1)の引数をint型の配列にしたもの
     * String#substring(int arg0, int arg1) = StrUtils#substring(int[] args)
     * StartIndex: arg0 = args[0]
     * EndIndex  : arg1 = args[1] - 1 (*1)
     *
     * (*1) 注意: args[1]は、EndIndex + 1である必要はなく、文字列のEndIndex位置そのままで良い。
     * (*1) NOTICE: args[1] must be index AS IS (not need EndIndex + 1).
     *
     * Example:
     *   StrUtil.substring("ABC123ABC", new int[]{3,5}) = "123"
     *   StrUtil.substring("ABC123ABC", new int[]{0,5}) = "ABC123"
     *   StrUtil.substring("ABC123ABC", new int[]{3,8}) = "123ABC"
     *   StrUtil.substring("ABC123ABC", null          ) = null
     *   StrUtil.substring(""         , new int[]{3,8}) = null
     *   StrUtil.substring( null      , new int[]{3,8}) = null
     *
     *   StrUtil.substring("ABC123ABC", new int[]{-1,8}) = ArrayIndexOutOfBoundsException
     *   StrUtil.substring("ABC123ABC", new int[]{0,-1}) = ArrayIndexOutOfBoundsException
     *   StrUtil.substring("ABC123ABC", new int[]{8,1})  = ArrayIndexOutOfBoundsException
     *
     * </pre>
     * @param value
     * @param region
     * @return
     */
    public static String substring(String value, int[] region) {
        if( isEmpty(value) || region == null ){
            return null;
        }
        if(region.length != 2){
            throw new IllegalArgumentException("int[] region size must be 2.");
        }
        if( region[0] < 0 || region[1] < 0 || region[0] > region[1]){
            throw new ArrayIndexOutOfBoundsException(
                "\n\t region[] is incorrect." +
                "\n\t region[0] = " + region[0] +
                "\n\t region[1] = " + region[1]
            );
        }
        return value.substring(region[0], region[1] + 1);
    }

    /**
     * <pre>
     * valueの指定位置から前方に、任意の長さで指定される文字列を返す。
     *
     * Example:
     *   StrUtil.prestring("ABC01234567", 3, 3 ) = "ABC"
     *   StrUtil.prestring("ABC01234567", 3, 2 ) =  "BC"
     *   StrUtil.prestring("ABC01234567", 3, 1 ) =   "C"
     *   StrUtil.prestring("ABC01234567", 2, 1 ) =   "B"
     *   StrUtil.prestring("ABC01234567", 1, 1 ) =   "A"
     *   StrUtil.prestring("ABC01234567", 0, 1 ) =  null
     *
     * </pre>
     * @param value 対象文字列
     * @param offset 基準となる位置
     * @param preStrLen 基準位置から前方に取得する文字数(基準位置の文字は含まず)
     * @return
     */
    public static String prestring(String value, int offset, int preStrLen){
        String rtn = value;
        if((offset - preStrLen) < 0 || offset < 0 || preStrLen < 1){
            throw new IllegalArgumentException(
                    "\n\t startIndex or preStrLength is not correct." +
                    "\n\t startIndex   = " + offset +
                    "\n\t preStrLength = " + preStrLen
            );
        }
        if(!isEmpty(value)){
            rtn = value.substring(offset - preStrLen, offset);
        }
        return rtn;
    }


    /**
     * <pre>
     * 第1引数のObject型配列srcを、第二引数のObject型配列dstにコピーする。
     * 第二引数のObject型配列dstは、事前にsrcと同じかそれ以上のサイズで
     * 生成されている必要がある。
     *
     * Example:
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[4];
     *   StrUtil.copyArray(src, dst);
     *   // Result: dst = {"000", "111", "222", "333"}
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[6];
     *   StrUtil.copyArray(src, dst);
     *   // Result: dst = {"000", "111", "222", "333", null, null}
     *
     *   String[] src = null;
     *   String[] dst = new String[5];
     *   StrUtil.copyArray(src, dst);
     *   // Result: IllegalArgumentException
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = null;
     *   StrUtil.copyArray(src, dst);
     *   // Result: IllegalArgumentException
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[2];
     *   StrUtil.copyArray(src, dst);
     *   // Result: IllegalArgumentException
     *
     *
     * </pre>
     * @param src
     * @param dst
     */
    public static void copyArray(Object[] src, Object[] dst) {
        if( isEmpty(src) ){
            throw new IllegalArgumentException(
                    "\n\t Array 'src' is empty or null."
                    );
        }
        copyArray(src, dst, 0, src.length - 1);
    }

    /**
     * <pre>
     * 第1引数のObject型配列srcを、startIndexで指定した配列インデックス以降を
     * 第二引数のObject型配列dstの先頭からへコピーする。
     * 第二引数のObject型配列dstは、事前にsrcと同じかそれ以上のサイズで
     * 生成されている必要がある。
     *
     * Example:
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[3];
     *   StrUtil.copyArray(src, dst, 1, 3);
     *   // Result: dst = {"111", "222", "333"}
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[3];
     *   StrUtil.copyArray(src, dst, 3, 3);
     *   // Result: dst = {"333", null, null}
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[2];
     *   StrUtil.copyArray(src, dst, 1, 2);
     *   // Result: dst = {"111", "222"}
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[1];
     *   StrUtil.copyArray(src, dst, 1, 2);
     *   // Result: IllegalArgumentException
     *
     *   String[] src = {"000", "111", "222", "333"};
     *   String[] dst = new String[3];
     *   StrUtil.copyArray(src, dst, 4, 3);
     *   // Result: ArrayIndexOutOfBoundsException
     *
     * </pre>
     * @param src
     * @param dst
     * @param startIndex
     */
    public static void copyArray(Object[] src, Object[] dst, int startIndex, int endIndex) {
        if( isEmpty(src) ){
            throw new IllegalArgumentException(
                    "\n\t Array 'src' is empty or null."
                    );
        }
        if( isEmpty(dst) ){
            throw new IllegalArgumentException("" +
                    "\n\t Array 'dst' is empty or null."
                    );
        }
        int srcLength = src.length;
        if(startIndex >= srcLength
                || endIndex >= srcLength
                || startIndex > endIndex){
            throw new ArrayIndexOutOfBoundsException(
                    "\n\t 'startIndex' or 'endIndex' is not correct." +
                    "\n\t startIndex = " + startIndex +
                    "\n\t endIndex   = " + endIndex +
                    "\n\t src.length = " + src.length
                    );
        }
        if(dst.length < (endIndex - startIndex)+1){
            throw new IllegalArgumentException(
                    "\n\t Length of 'dst' is not correct."+
                    "\n\t   startIndex = " + startIndex +
                    "\n\t   endIndex   = " + endIndex +
                    "\n\t   src.length = " + src.length +
                    "\n\t   dst.length = " + dst.length
            );
        }

        int indexDst = 0;
        for(int i=startIndex; i<endIndex+1; i++){
            dst[indexDst++] = src[i];
        }

    }


    /**
     * <pre>
     * List形式で与えられた文字列リストをString型の配列で返す
     *
     * </pre>
     * @param list
     * @return List形式で与えられた文字列リストをString型配列に変換したもの
     */
    public static String[] toArray(java.util.List<String> list){
        return (String[])list.toArray(new String[list.size()]);
    }


    /**
     * <pre>
     * 引数で渡された配列の中身をtoString[]し、"["、"]"で囲んで１行に連続表示。
     * ネストした配列には対応しない。
     *
     * Example:
     *   StrUtil.toStringFromArray(new String[] {"0","1","2"}, false) = "[0], [1], [2]"
     *   StrUtil.toStringFromArray(new String[] {"0","1","2"}, true ) = "{"0", "1", "2"}"
     *   StrUtil.toStringFromArray(new String[] {""},          false) = "[]"
     *   StrUtil.toStringFromArray(new String[] {""},          true ) = "{""}"
     *   StrUtil.toStringFromArray(new String[] {},            true ) = "array is null or 0-length."
     *   StrUtil.toStringFromArray(null,                       false) = "array is null or 0-length."
     *
     * </pre>
     * @param list
     * @param enableDblQuote 結果をダブるクォートで囲むならtrue
     * @return
     */
    public static String toStringFromArray(Object[] list, boolean enableDblQuote){
        StringBuilder sb = new StringBuilder();
        if(list == null || list.length == 0){
            sb.append("array is null or 0-length.");
            return sb.toString() ;
        }
        if(enableDblQuote){
            sb.append("{");
        }
        for(int i=0; i<list.length-1; i++){
            String tmp = list[i].toString();
            if(enableDblQuote){
                tmp = replace(tmp, "\\", "\\\\", false);
                tmp = replace(tmp, "\"", "\\\"", false);
                sb.append("\"" + tmp + "\", ");
            }else{
                sb.append("[" + tmp + "], ");
            }
        }
        String tmp = list[list.length-1].toString();
        if(enableDblQuote){
            tmp = replace(tmp, "\\", "\\\\", false);
            tmp = replace(tmp, "\"", "\\\"", false);
            sb.append("\"" + tmp + "\"");
            sb.append("}");
        }else{
            sb.append("[" + tmp + "]");
        }
        return sb.toString() ;
    }


    /**
     * <pre>
     * 入力された文字列中に含まれるコントロールコードを
     * "\"でエンコードした状態に変換する。
     * 対象コントロールコードは、
     * 改行コード(CR,LF)、タブ、バックスラッシュ"\"の４つ。
     *
     * </pre>
     * @param value
     * @return
     */
    public static String toStringWithCtrlCodeEncoding(String value){
        String rtn = value;
        rtn = replace(rtn, "\\", "\\\\", false);
        rtn = replace(rtn, "\n", "\\n", false);
        rtn = replace(rtn, "\r", "\\r", false);
        rtn = replace(rtn, "\t", "\\t", false);
        return rtn;
    }

    public static String toStringWithDblQuoteEncoding(String value){
        String rtn = value;
        rtn = replace(rtn, "\"", "\\\"", false);
        return rtn;
    }


}
